From dd2d03e035ec553d6064398b58c09ff12156b7d4 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Tue, 4 Jun 2024 08:41:12 +0200 Subject: [PATCH 01/19] Fix context in ubuntu 24 --- contextualization/conf-ansible.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/contextualization/conf-ansible.yml b/contextualization/conf-ansible.yml index 1da87a2c..8e0b9e59 100644 --- a/contextualization/conf-ansible.yml +++ b/contextualization/conf-ansible.yml @@ -155,11 +155,26 @@ extra_args: --prefer-binary --break-system-packages when: ansible_python_version is version('3.11', '>=') - - name: Install cryptography & pyOpenSSL & pyyaml & wheel + - name: Install cryptography & pyOpenSSL in py3.11- pip: name: - cryptography>36.0.0,<39.0.0 - pyOpenSSL>20.0,<22.1.0 + executable: pip3 + extra_args: "{{ extra_args }}" + when: ansible_python_version is version('3.11', '<') + + - name: Install cryptography & pyOpenSSL in py3.11+ + pip: + name: cryptography>36.0.0 + name: pyOpenSSL>20.0 + executable: pip3 + extra_args: "{{ extra_args }}" + when: ansible_python_version is version('3.11', '>=') + + - name: Install pyyaml, wheel, paramiko and packaging + pip: + name: - wheel - pyyaml - paramiko>=2.9.5 From 3f34c81e1ec45d15e6f0dd9163e8388ccdbfbcaf Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Tue, 4 Jun 2024 09:50:26 +0200 Subject: [PATCH 02/19] Fix context in old distros --- contextualization/conf-ansible.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contextualization/conf-ansible.yml b/contextualization/conf-ansible.yml index 8e0b9e59..499614d2 100644 --- a/contextualization/conf-ansible.yml +++ b/contextualization/conf-ansible.yml @@ -125,6 +125,8 @@ pip: name: pip>18.0,<21.0 executable: pip3 + extra_args: --trusted-host files.pythonhosted.org --trusted-host pypi.org --trusted-ho +st pypi.python.org when: ansible_python_version is version('3.7', '<') - name: Upgrade pip in py3.7-py3.8 From fed9cf0b7f2545f715ea415ef17fc9a0ac43f138 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Tue, 4 Jun 2024 09:55:38 +0200 Subject: [PATCH 03/19] Fix typo --- contextualization/conf-ansible.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contextualization/conf-ansible.yml b/contextualization/conf-ansible.yml index 499614d2..6f84ac94 100644 --- a/contextualization/conf-ansible.yml +++ b/contextualization/conf-ansible.yml @@ -125,8 +125,7 @@ pip: name: pip>18.0,<21.0 executable: pip3 - extra_args: --trusted-host files.pythonhosted.org --trusted-host pypi.org --trusted-ho -st pypi.python.org + 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 From 5943edcdce1727e244e4905ac392d8e72318ea32 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Tue, 4 Jun 2024 09:58:12 +0200 Subject: [PATCH 04/19] Add comment --- contextualization/conf-ansible.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/contextualization/conf-ansible.yml b/contextualization/conf-ansible.yml index 6f84ac94..67a03f29 100644 --- a/contextualization/conf-ansible.yml +++ b/contextualization/conf-ansible.yml @@ -125,6 +125,7 @@ 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', '<') From 9e732baaff1780ff503f765a970df909e6e151e6 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Tue, 4 Jun 2024 16:42:28 +0200 Subject: [PATCH 05/19] Add info about getting EGI token --- doc/source/REST.rst | 2 ++ doc/source/client.rst | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/doc/source/REST.rst b/doc/source/REST.rst index d8f8f65d..56bda17f 100644 --- a/doc/source/REST.rst +++ b/doc/source/REST.rst @@ -14,6 +14,8 @@ using "\\n" as separator. If the content cannot be parsed successfully, or the u password are not valid, it is returned the HTTP error code 401. In case that Vault 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 b4601c31..2d96a01f 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 ++++++++++++++++++ From 869dfc7bdc64c261b4a7761971df685b68176f21 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Wed, 5 Jun 2024 09:52:02 +0200 Subject: [PATCH 06/19] Minor change --- doc/source/REST.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/REST.rst b/doc/source/REST.rst index 56bda17f..3334f41b 100644 --- a/doc/source/REST.rst +++ b/doc/source/REST.rst @@ -14,6 +14,7 @@ using "\\n" as separator. If the content cannot be parsed successfully, or the u password are not valid, it is returned the HTTP error code 401. In case that Vault 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. From 97416c9339ec1a94230f6590f7948a0a4127ed5c Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Wed, 5 Jun 2024 11:22:59 +0200 Subject: [PATCH 07/19] Move REST to flask --- IM/REST.py | 456 ++++++++++++++--------------------------- requirements-tests.txt | 3 +- setup.py | 4 +- 3 files changed, 156 insertions(+), 307 deletions(-) diff --git a/IM/REST.py b/IM/REST.py index 91b28f33..3c888555 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, @@ -54,120 +57,44 @@ 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 - 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) + try: + server = WSGIServer((host, port), PathInfoDispatcher({'/': app})) + if Config.REST_SSL: + server.ssl_adapter = BuiltinSSLAdapter(Config.REST_SSL_CERTFILE, + Config.REST_SSL_KEYFILE, + Config.REST_SSL_CA_CERTS) + + server.start() + except KeyboardInterrupt: + server.stop() + 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.make_response(json.dumps({'message': msg, 'code': code}), code, {'Content-Type': '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.make_response(HTML_ERROR_TEMPLATE % (code, code, msg), code, {'Content-Type': '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.make_response(msg, code, {'Content-Type': 'text/plain'}) def stop(): - if bottle_server: - bottle_server.shutdown() + pass def get_media_type(header): @@ -176,7 +103,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 +126,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,7 +192,7 @@ 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={}): """ Format the output of the API responses """ @@ -307,7 +234,9 @@ def format_output(res, default_type="text/plain", field_name=None, list_field_na break if content_type: - bottle.response.content_type = content_type + headers = {'Content-Type': content_type} + headers.update(extra_headers) + return flask.make_response(info, 200, headers) else: return return_error(415, "Unsupported Accept Media Types: %s" % ",".join(accept)) else: @@ -318,23 +247,23 @@ def format_output(res, default_type="text/plain", field_name=None, list_field_na info = "\n".join(res) else: info = "%s" % res - bottle.response.content_type = default_type - - return info + headers = {'Content-Type': default_type} + headers.update(extra_headers) + return flask.make_response(info, 200, {'Content-Type': default_type}) -@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' -@app.route('/infrastructures/:infid', method='DELETE') +@app.route('/infrastructures/', methods=['DELETE']) def RESTDestroyInfrastructure(infid=None): try: auth = get_auth_header() @@ -343,8 +272,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 +282,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 +292,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 +308,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 +320,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 +334,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 +344,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 +360,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 +389,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 +417,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 +426,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 +443,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 +452,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 +466,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 +497,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 +508,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 +517,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 +525,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 +537,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 +562,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 +577,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 +666,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 +675,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 +685,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 +716,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 +723,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 +746,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 +755,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 +765,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 +783,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 +792,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 +825,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 +834,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 +857,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'}, extra_headers={'InfID': infid}) except DeletedInfrastructureException as ex: return return_error(404, "Error reconfiguring infrastructure: %s" % get_ex_error(ex)) except IncorrectInfrastructureException as ex: @@ -956,134 +874,69 @@ 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): +@app.route('/infrastructures//', methods=['PUT']) +def RESTStartInfrastructure(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.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): - 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 in infrastructure: %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 %s operation in infrastructure: %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 %s operation in infrastructure: %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 %s operation in infrastructure: %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 %s operation in infrastructure" % op) + return return_error(400, "Error %s operation in infrastructure: %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//', method='PUT') +def RESTStartVM(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 {} @@ -1097,7 +950,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 +958,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 +973,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 +1003,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 +1013,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 +1030,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 +1039,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 +1051,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 +1059,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 +1077,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) -@app.error(404) +@app.errorhandler(404) def error_mesage_404(error): return return_error(404, error.body) -@app.error(405) +@app.errorhandler(405) def error_mesage_405(error): return return_error(405, error.body) -@app.error(500) +@app.errorhandler(500) def error_mesage_500(error): return return_error(500, error.body) diff --git a/requirements-tests.txt b/requirements-tests.txt index e08ffc8c..48bdd4d1 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 dfb350ae..2423968b 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'] ) From 83d72fdc8f67d63ba38ad9af2e73e8a0e1742c06 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Wed, 5 Jun 2024 12:09:06 +0200 Subject: [PATCH 08/19] Move REST to flask --- IM/REST.py | 11 +- test/unit/REST.py | 1080 ++------------------------------------------- 2 files changed, 45 insertions(+), 1046 deletions(-) diff --git a/IM/REST.py b/IM/REST.py index 3c888555..f83cca6b 100644 --- a/IM/REST.py +++ b/IM/REST.py @@ -261,6 +261,7 @@ def enable_cors(response): 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/', methods=['DELETE']) @@ -420,7 +421,7 @@ def RESTGetInfrastructureProperty(infid=None, prop=None): @app.route('/infrastructures', methods=['GET']) def RESTGetInfrastructureList(): try: - auth = get_auth_header() + auth = get_auth_header() except Exception: return return_error(401, "No authentication data provided") @@ -875,7 +876,7 @@ def RESTReconfigureInfrastructure(infid=None): @app.route('/infrastructures//', methods=['PUT']) -def RESTStartInfrastructure(infid=None, op=None): +def RESTOperateInfrastructure(infid=None, op=None): try: auth = get_auth_header() except Exception: @@ -902,8 +903,8 @@ def RESTStartInfrastructure(infid=None, op=None): return return_error(400, "Error %s operation in infrastructure: %s" % (op, get_ex_error(ex))) -@app.route('/infrastructures//vms//', method='PUT') -def RESTStartVM(infid=None, vmid=None, op=None): +@app.route('/infrastructures//vms//', methods=['PUT']) +def RESTOperateVM(infid=None, vmid=None, op=None): try: auth = get_auth_header() except Exception: @@ -941,7 +942,7 @@ def ReturnOptions(**kwargs): return {} -@app.route('/version', method='GET') +@app.route('/version') def RESTGetVersion(): try: from IM import __version__ as version diff --git a/test/unit/REST.py b/test/unit/REST.py index 4b3ac4ec..5492793b 100755 --- a/test/unit/REST.py +++ b/test/unit/REST.py @@ -37,29 +37,7 @@ 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,1040 +52,60 @@ 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.") - - @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, - 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")} - - GetInfrastructureState.return_value = {'state': "running", 'vm_states': {"vm1": "running", "vm2": "running"}} - GetInfrastructureRADL.return_value = "radl" - GetInfrastructureContMsg.return_value = "contmsg" - - inf = MagicMock() - get_infrastructure.return_value = inf - tosca = MagicMock() - inf.extra_info = {"TOSCA": tosca} - tosca.get_outputs.return_value = "outputs" - tosca.serialize.return_value = "tosca" - - res = RESTGetInfrastructureProperty("1", "state") - self.assertEqual(json.loads(res)["state"]["state"], "running") - - res = RESTGetInfrastructureProperty("1", "contmsg") - self.assertEqual(res, "contmsg") - - bottle_request.params = {'headeronly': 'yes'} - res = RESTGetInfrastructureProperty("1", "contmsg") - self.assertEqual(res, "contmsg") - - bottle_request.params = {'headeronly': 'no'} - res = RESTGetInfrastructureProperty("1", "contmsg") - self.assertEqual(res, "contmsg") - - res = RESTGetInfrastructureProperty("1", "radl") - self.assertEqual(res, "radl") - - res = RESTGetInfrastructureProperty("1", "outputs") - self.assertEqual(res, '{"outputs": "outputs"}') - - res = RESTGetInfrastructureProperty("1", "tosca") - self.assertEqual(res, "tosca") - - GetInfrastructureRADL.side_effect = DeletedInfrastructureException() - res = RESTGetInfrastructureProperty("1", "radl") - self.assertEqual(res, "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.") - - GetInfrastructureRADL.side_effect = UnauthorizedUserException() - res = RESTGetInfrastructureProperty("1", "radl") - self.assertEqual(res, "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, "") - 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, "") - 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, "") - 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.") - - DestroyInfrastructure.side_effect = IncorrectInfrastructureException() - res = RESTDestroyInfrastructure("1") - self.assertEqual(res, "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.") - - DestroyInfrastructure.side_effect = IncorrectStateException() - res = RESTDestroyInfrastructure("1") - self.assertEqual(res, "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): - """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") - - CreateInfrastructure.return_value = "1" - - res = RESTCreateInfrastructure() - self.assertEqual(res, "http://imserver.com/infrastructures/1") - - bottle_request.params = {"async": "yes"} - res = RESTCreateInfrastructure() - self.assertEqual(res, "http://imserver.com/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") - - CreateInfrastructure.return_value = "1" - - res = RESTCreateInfrastructure() - self.assertEqual(res, "http://imserver.com/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") - CreateInfrastructure.side_effect = InvaliddUserException() - res = RESTCreateInfrastructure() - self.assertEqual(res, "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": []}}')) - - @patch("IM.InfrastructureManager.InfrastructureManager.CreateInfrastructure") - @patch("bottle.request") - def test_CreateInfrastructureWithErrors(self, bottle_request, 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") - - CreateInfrastructure.return_value = "1" - - res = RESTCreateInfrastructure() - res_json = json.loads(res) - self.assertEqual(res_json['code'], 415) - - @patch("IM.InfrastructureManager.InfrastructureManager.GetVMInfo") - @patch("bottle.request") - def test_GetVMInfo(self, bottle_request, 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"} - - 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"}]}')) - - bottle_request.headers["Accept"] = "text/*" - res = RESTGetVMInfo("1", "1") - self.assertEqual(res, '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.") - - GetVMInfo.side_effect = IncorrectInfrastructureException() - res = RESTGetVMInfo("1", "1") - self.assertEqual(res, "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.") - - GetVMInfo.side_effect = DeletedVMException() - res = RESTGetVMInfo("1", "1") - self.assertEqual(res, "Error Getting VM. info: Deleted VM.") - - GetVMInfo.side_effect = IncorrectVMException() - res = RESTGetVMInfo("1", "1") - self.assertEqual(res, "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): - """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")} - - GetVMProperty.return_value = "prop" - GetVMContMsg.return_value = "contmsg" - - res = RESTGetVMProperty("1", "1", "prop") - self.assertEqual(res, "prop") - - res = RESTGetVMProperty("1", "1", "contmsg") - self.assertEqual(res, "contmsg") - - GetVMProperty.side_effect = DeletedInfrastructureException() - res = RESTGetVMProperty("1", "1", "prop") - self.assertEqual(res, "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.") - - GetVMProperty.side_effect = UnauthorizedUserException() - res = RESTGetVMProperty("1", "1", "prop") - self.assertEqual(res, "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.") - - GetVMProperty.side_effect = IncorrectVMException() - res = RESTGetVMProperty("1", "1", "prop") - self.assertEqual(res, "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): - """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'} - - 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 = 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": "text/yaml"} - bottle_request.body = read_file_as_bytes("../files/tosca_simple.yml") - - 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") - AddResource.side_effect = DeletedInfrastructureException() - res = RESTAddResource("1") - self.assertEqual(res, "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.") - - 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.") - - @patch("IM.InfrastructureManager.InfrastructureManager.RemoveResource") - @patch("bottle.request") - def test_RemoveResource(self, bottle_request, 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'} - - RemoveResource.return_value = 2 - - res = RESTRemoveResource("1", "1,2") - self.assertEqual(res, "") - - RemoveResource.side_effect = DeletedInfrastructureException() - res = RESTRemoveResource("1", "1,2") - self.assertEqual(res, "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.") - - RemoveResource.side_effect = UnauthorizedUserException() - res = RESTRemoveResource("1", "1,2") - self.assertEqual(res, "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.") - - RemoveResource.side_effect = IncorrectVMException() - res = RESTRemoveResource("1", "1,2") - self.assertEqual(res, "Error Removing resources: Invalid VM ID") - - @patch("IM.InfrastructureManager.InfrastructureManager.AlterVM") - @patch("bottle.request") - def test_AlterVM(self, bottle_request, 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'} - - 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 = 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": "application/json"} - bottle_request.body = read_file_as_bytes("../files/test_simple.json") - - 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.") - - 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.") - - 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.") - - 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.") - - 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") - - @patch("IM.InfrastructureManager.InfrastructureManager.Reconfigure") - @patch("bottle.request") - def test_Reconfigure(self, bottle_request, 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'} - - 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 = RESTReconfigureInfrastructure("1") - self.assertEqual(res, "") - - 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.") - - 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.") - - 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.") - - @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.") - - @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_GeVersion(self): - res = RESTGetVersion() - self.assertEqual(res, version) - - @patch("IM.InfrastructureManager.InfrastructureManager.CreateDiskSnapshot") - @patch("bottle.request") - def test_CreateDiskSnapshot(self, bottle_request, 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")} - - 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") - - CreateDiskSnapshot.side_effect = DeletedInfrastructureException() - res = RESTCreateDiskSnapshot("1", "1", 0) - self.assertEqual(res, "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.") - - CreateDiskSnapshot.side_effect = UnauthorizedUserException() - res = RESTCreateDiskSnapshot("1", "1", 0) - self.assertEqual(res, "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.") - - CreateDiskSnapshot.side_effect = IncorrectVMException() - res = RESTCreateDiskSnapshot("1", "1", 0) - self.assertEqual(res, "Error creating snapshot: Invalid VM ID") - - @patch("IM.InfrastructureManager.InfrastructureManager.ExportInfrastructure") - @patch("bottle.request") - def test_ExportInfrastructure(self, bottle_request, 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")} - - ExportInfrastructure.return_value = "strinf" - - res = RESTGetInfrastructureProperty("1", "data") - self.assertEqual(res, '{"data": "strinf"}') - - @patch("IM.InfrastructureManager.InfrastructureManager.ImportInfrastructure") - @patch("bottle.request") - def test_ImportInfrastructure(self, bottle_request, 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'} - - 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') - - @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): - """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'} - - 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") - auth_str = "Authorization: type = InfrastructureManager; username = user; password = pass" - url = "http://imserver.com/infrastructures/1/vms/1/command?step=2" - ps_command = "ps aux | grep -v grep | grep 'ssh -N -R'" - expected_res = """ - res="wait" - while [ "$res" == "wait" ] - do - res=`curl --insecure -s -H "%s" -H "Accept: text/plain" %s` - if [ "$res" != "wait" ] - then - echo "$res" > /var/tmp/reverse_ssh.sh - chmod a+x /var/tmp/reverse_ssh.sh - /var/tmp/reverse_ssh.sh - if [ "$res" != "true" ] - then - echo "*/1 * * * * root %s || /var/tmp/reverse_ssh.sh" > /etc/cron.d/reverse_ssh - fi - else - sleep 20 - fi - done""" % (auth_str, url, ps_command) - self.assertEqual(res, expected_res) - - inf.auth = Authentication([{'type': 'InfrastructureManager', 'token': 'token'}]) - res = RESTGetVMProperty("1", "1", "command") - auth_str = "Authorization: type = InfrastructureManager; token = token" - url = "http://imserver.com/infrastructures/1/vms/1/command?step=2" - expected_res = """ - res="wait" - while [ "$res" == "wait" ] - do - res=`curl --insecure -s -H "%s" -H "Accept: text/plain" %s` - if [ "$res" != "wait" ] - then - echo "$res" > /var/tmp/reverse_ssh.sh - chmod a+x /var/tmp/reverse_ssh.sh - /var/tmp/reverse_ssh.sh - if [ "$res" != "true" ] - then - echo "*/1 * * * * root %s || /var/tmp/reverse_ssh.sh" > /etc/cron.d/reverse_ssh - fi - else - sleep 20 - fi - done""" % (auth_str, url, ps_command) - self.assertEqual(res, expected_res) - - radl_master = parse_radl(""" - network publica (outbound = 'yes') - network privada () - - system front ( - cpu.arch='x86_64' and - cpu.count>=1 and - memory.size>=512m and - net_interface.0.ip = '8.8.8.8' and - net_interface.0.connection = 'publica' and - net_interface.1.connection = 'privada' and - disk.0.image.url = 'mock0://linux.for.ev.er' and - disk.0.os.credentials.username = 'ubuntu' and - disk.0.os.credentials.password = 'yoyoyo' and - disk.0.os.name = 'linux' - ) - """) - - radl_vm1 = parse_radl(""" - network privada () - - system wn ( - cpu.arch='x86_64' and - cpu.count>=1 and - memory.size>=512m and - net_interface.0.connection = 'privada' and - disk.0.image.url = 'mock0://linux.for.ev.er' and - disk.0.os.credentials.username = 'ubuntu' and - disk.0.os.credentials.password = 'yoyoyo' and - disk.0.os.name = 'linux' - ) - """) - - radl_vm2 = parse_radl(""" - network privada2 () - - system wn2 ( - cpu.arch='x86_64' and - cpu.count>=1 and - memory.size>=512m and - net_interface.0.connection = 'privada2' and - disk.0.image.url = 'mock0://linux.for.ev.er' and - disk.0.os.credentials.username = 'ubuntu' and - disk.0.os.credentials.password = 'yoyoyo' and - disk.0.os.name = 'linux' - ) - """) - - # 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() - ssh.test_connectivity.return_value = True - ssh.port = 22 - ssh.private_key = None - ssh.password = "yoyoyo" - ssh.username = "ubuntu" - ssh.host = "8.8.8.8" - SSH.return_value = ssh - vm1 = VirtualMachine(inf, None, None, radl_vm1, radl_vm1) - vm1.creation_im_id = 1 - vm1.destroy = False - vm2 = VirtualMachine(inf, None, None, radl_vm2, radl_vm2) - vm2.creation_im_id = 2 - vm2.destroy = False - inf.vm_list = [inf.vm_master, vm1, vm2] - - res = RESTGetVMProperty("1", "0", "command") - expected_res = "true" - self.assertEqual(res, expected_res) - - bottle_request.params = {'step': '2'} - res = RESTGetVMProperty("1", "1", "command") - expected_res = "true" - self.assertEqual(res, expected_res) - - # in VM not connected to the Master VM - res = RESTGetVMProperty("1", "2", "command") - 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) - - @patch("bottle.request") - def test_GetCloudInfo(self, bottle_request): - """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") - @patch("IM.InfrastructureManager.InfrastructureManager.GetCloudImageList") - def test_GetCloudInfo_filters(self, GetCloudImageList, bottle_request): - """Test REST GetCloudInfo with filters.""" - bottle_request.return_value = MagicMock() - bottle_request.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": []}) - 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): - """Test REST ChangeInfrastructureAuth.""" - bottle_request.return_value = MagicMock() - bottle_request.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") - 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", - "password": "new_pass"}]) - 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): - """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")} - - GetInfrastructureOwners.return_value = ["user1", "user2"] - - res = RESTGetInfrastructureProperty("1", "authorization") - self.assertEqual(res, 'user1\nuser2') - - bottle_request.headers["Accept"] = "application/json" - res = RESTGetInfrastructureProperty("1", "authorization") - self.assertEqual(res, '{"authorization": ["user1", "user2"]}') - + 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.") if __name__ == "__main__": unittest.main() From 1bddcf3faf9eba769e67df85419ef3423ec651cf Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Wed, 5 Jun 2024 12:29:08 +0200 Subject: [PATCH 09/19] Move tests --- test/unit/REST.py | 131 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/test/unit/REST.py b/test/unit/REST.py index 5492793b..1e2ccfd0 100755 --- a/test/unit/REST.py +++ b/test/unit/REST.py @@ -107,5 +107,136 @@ def test_GetInfrastructureInfo(self, GetInfrastructureInfo): 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") + def test_GetInfrastructureProperty(self, get_infrastructure, GetInfrastructureState, + GetInfrastructureRADL, GetInfrastructureContMsg): + 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" + GetInfrastructureContMsg.return_value = "contmsg" + + inf = MagicMock() + get_infrastructure.return_value = inf + tosca = MagicMock() + inf.extra_info = {"TOSCA": tosca} + tosca.get_outputs.return_value = "outputs" + tosca.serialize.return_value = "tosca" + + res = self.client.get('/infrastructures/1/state', headers=headers) + self.assertEqual(res.json["state"]["state"], "running") + + res = self.client.get('/infrastructures/1/contmsg', headers=headers) + self.assertEqual(res.text, "contmsg") + + res = self.client.get('/infrastructures/1/contmsg?headeronly=yes', headers=headers) + self.assertEqual(res.text, "contmsg") + + res = self.client.get('/infrastructures/1/contmsg?headeronly=no', headers=headers) + self.assertEqual(res.text, "contmsg") + + res = self.client.get('/infrastructures/1/radl', headers=headers) + self.assertEqual(res.text, "radl") + + res = self.client.get('/infrastructures/1/outputs', headers=headers) + self.assertEqual(res.json, {"outputs": "outputs"}) + + res = self.client.get('/infrastructures/1/tosca', headers=headers) + self.assertEqual(res.text, "tosca") + + GetInfrastructureRADL.side_effect = DeletedInfrastructureException() + res = self.client.get('/infrastructures/1/radl', headers=headers) + self.assertEqual(res.text, "Error Getting Inf. prop: Deleted infrastructure.") + + GetInfrastructureRADL.side_effect = IncorrectInfrastructureException() + 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 = 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") + 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) + + 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) + + 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 = self.client.delete('/infrastructures/1', headers=headers) + self.assertEqual(res.text, "Error Destroying Inf: Deleted infrastructure.") + + DestroyInfrastructure.side_effect = IncorrectInfrastructureException() + 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 = 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 = 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") + def test_CreateInfrastructure(self, get_infrastructure, CreateInfrastructure): + """Test REST CreateInfrastructure.""" + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} + CreateInfrastructure.return_value = "1" + + res = self.client.post('/infrastructures', headers=headers, data=BytesIO(b"radl")) + self.assertEqual(res.text, "http://localhost/infrastructures/1") + + res = self.client.post('/infrastructures?async=yes', headers=headers, data=BytesIO(b"radl")) + self.assertEqual(res.text, "http://localhost/infrastructures/1") + + 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") + + 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") + + 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://onedock.i3m.upv.es:2633", + "compute": [{"cpuCores": 1, "memoryInMegabytes": 1024}, + {"cpuCores": 1, "memoryInMegabytes": 1024}], "storage": []}}) + + headers["Content-Type"] = "application/json" + CreateInfrastructure.side_effect = InvaliddUserException() + 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") + + CreateInfrastructure.side_effect = UnauthorizedUserException() + 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.") + if __name__ == "__main__": unittest.main() From 04465d6c6ae1deced6872436d70b08cdebe17c19 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Wed, 5 Jun 2024 16:19:45 +0200 Subject: [PATCH 10/19] Move to flask --- IM/REST.py | 29 ++- test/unit/REST.py | 582 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 591 insertions(+), 20 deletions(-) diff --git a/IM/REST.py b/IM/REST.py index f83cca6b..5cd371fa 100644 --- a/IM/REST.py +++ b/IM/REST.py @@ -81,16 +81,15 @@ def run(host, port): server.stop() - def return_error(code, msg): content_type = get_media_type('Accept') if "application/json" in content_type: - return flask.make_response(json.dumps({'message': msg, 'code': code}), code, {'Content-Type': 'application/json'}) + return flask.Response(json.dumps({'message': msg, 'code': code}), status=code, mimetype='application/json') elif "text/html" in content_type: - return flask.make_response(HTML_ERROR_TEMPLATE % (code, code, msg), code, {'Content-Type': 'text/html'}) + return flask.Response(HTML_ERROR_TEMPLATE % (code, code, msg), status=code, mimetype='text/html') else: - return flask.make_response(msg, code, {'Content-Type': 'text/plain'}) + return flask.Response(msg, status=code, mimetype='text/plain') def stop(): @@ -861,7 +860,7 @@ def RESTReconfigureInfrastructure(infid=None): res = InfrastructureManager.Reconfigure(infid, radl_data, auth, vm_list) # As we have to reconfigure the infra, return the ID for the HAProxy stickiness - return flask.make_response(res, 200, {'Content-Type': 'text/plain'}, extra_headers={'InfID': infid}) + 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: @@ -891,16 +890,16 @@ def RESTOperateInfrastructure(infid=None, op=None): flask.abort(404) return flask.make_response(res, 200, {'Content-Type': 'text/plain'}) except DeletedInfrastructureException as ex: - return return_error(404, "Error in %s operation in infrastructure: %s" % (op, 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 %s operation in infrastructure: %s" % (op, 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 %s operation in infrastructure: %s" % (op, 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 %s operation in infrastructure: %s" % (op, 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 %s operation in infrastructure" % op) - return return_error(400, "Error %s operation in infrastructure: %s" % (op, 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//vms//', methods=['PUT']) @@ -1080,19 +1079,19 @@ def RESTChangeInfrastructureAuth(infid=None): @app.errorhandler(403) def error_mesage_403(error): - return return_error(403, error.body) + return return_error(403, error.description) @app.errorhandler(404) def error_mesage_404(error): - return return_error(404, error.body) + return return_error(404, error.description) @app.errorhandler(405) def error_mesage_405(error): - return return_error(405, error.body) + return return_error(405, error.description) @app.errorhandler(500) def error_mesage_500(error): - return return_error(500, error.body) + return return_error(500, error.description) diff --git a/test/unit/REST.py b/test/unit/REST.py index 1e2ccfd0..16b3e02f 100755 --- a/test/unit/REST.py +++ b/test/unit/REST.py @@ -37,7 +37,7 @@ UnauthorizedUserException, InvaliddUserException) from IM.InfrastructureInfo import IncorrectVMException, DeletedVMException, IncorrectStateException -from IM.REST import app +from IM.REST import app, return_error, format_output def read_file_as_bytes(file_name): @@ -199,11 +199,10 @@ def test_DestroyInfrastructure(self, DestroyInfrastructure): self.assertEqual(res.text, "Error Destroying Inf: Invalid State to perform this operation.") @patch("IM.InfrastructureManager.InfrastructureManager.CreateInfrastructure") - @patch("IM.InfrastructureManager.InfrastructureManager.get_infrastructure") - def test_CreateInfrastructure(self, get_infrastructure, CreateInfrastructure): + def test_CreateInfrastructure(self, CreateInfrastructure): """Test REST CreateInfrastructure.""" headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "id = one; type = OpenNebula; host = ramses.i3m.upv.es:2633; " "username = user; password = pass")} CreateInfrastructure.return_value = "1" @@ -225,7 +224,7 @@ def test_CreateInfrastructure(self, get_infrastructure, CreateInfrastructure): # 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://onedock.i3m.upv.es:2633", + "cloudEndpoint": "http://ramses.i3m.upv.es:2633", "compute": [{"cpuCores": 1, "memoryInMegabytes": 1024}, {"cpuCores": 1, "memoryInMegabytes": 1024}], "storage": []}}) @@ -238,5 +237,578 @@ def test_CreateInfrastructure(self, get_infrastructure, CreateInfrastructure): 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") + def test_CreateInfrastructureWithErrors(self, CreateInfrastructure): + """Test REST CreateInfrastructure.""" + 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 = self.client.post('/infrastructures', headers=headers, data=BytesIO(b"radl")) + self.assertEqual(res.json['code'], 415) + + @patch("IM.InfrastructureManager.InfrastructureManager.GetVMInfo") + def test_GetVMInfo(self, GetVMInfo): + """Test REST GetVMInfo.""" + 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 = self.client.get('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.json, {"radl": [{"cpu.count": 1, "class": "system", "id": "test"}]}) + + 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 = self.client.get('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, "Error Getting VM. info: Deleted infrastructure.") + + GetVMInfo.side_effect = IncorrectInfrastructureException() + 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 = 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 = self.client.get('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, "Error Getting VM. info: Deleted VM.") + + GetVMInfo.side_effect = IncorrectVMException() + 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") + def test_GetVMProperty(self, GetVMContMsg, GetVMProperty): + """Test REST GetVMProperty.""" + 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 = self.client.get('/infrastructures/1/vms/1/prop', headers=headers) + self.assertEqual(res.text, "prop") + + res = self.client.get('/infrastructures/1/vms/1/contmsg', headers=headers) + self.assertEqual(res.text, "contmsg") + + GetVMProperty.side_effect = DeletedInfrastructureException() + 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 = 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 = 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 = 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 = 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") + def test_AddResource(self, get_infrastructure, AddResource): + """Test REST AddResource.""" + 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 = self.client.post('/infrastructures/1?context=yes', headers=headers, data=BytesIO(b"radl")) + self.assertEqual(res.text, "http://localhost/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") + + 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") + + headers["Content-Type"] = "application/json" + AddResource.side_effect = DeletedInfrastructureException() + 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.") + + AddResource.side_effect = IncorrectInfrastructureException() + 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.") + + AddResource.side_effect = UnauthorizedUserException() + 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") + def test_RemoveResource(self, RemoveResource): + """Test REST RemoveResource.""" + 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 = self.client.delete('/infrastructures/1/vms/1,2?context=yes', headers=headers) + self.assertEqual(res.text, "") + + RemoveResource.side_effect = DeletedInfrastructureException() + res = self.client.delete('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, "Error Removing resources: Deleted infrastructure.") + + RemoveResource.side_effect = IncorrectInfrastructureException() + 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 = 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 = self.client.delete('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, "Error Removing resources: Deleted VM.") + + RemoveResource.side_effect = IncorrectVMException() + 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") + def test_AlterVM(self, AlterVM): + """Test REST AlterVM.""" + 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 = self.client.put('/infrastructures/1/vms/1?context=yes', headers=headers, data=BytesIO(b"radl")) + self.assertEqual(res.text, "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") + + 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") + + AlterVM.side_effect = DeletedInfrastructureException() + 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.") + + AlterVM.side_effect = IncorrectInfrastructureException() + 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.") + + AlterVM.side_effect = UnauthorizedUserException() + 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.") + + AlterVM.side_effect = DeletedVMException() + 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.") + + AlterVM.side_effect = IncorrectVMException() + 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") + def test_Reconfigure(self, Reconfigure): + """Test REST Reconfigure.""" + 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 = self.client.put('/infrastructures/1/reconfigure?vmlist=1,2', headers=headers, data=BytesIO(b"radl")) + self.assertEqual(res.text, "") + + 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, "") + + Reconfigure.side_effect = DeletedInfrastructureException() + 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.") + + Reconfigure.side_effect = IncorrectInfrastructureException() + 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.") + + Reconfigure.side_effect = UnauthorizedUserException() + 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("IM.InfrastructureManager.InfrastructureManager.StopInfrastructure") + 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("IM.InfrastructureManager.InfrastructureManager.StopVM") + @patch("IM.InfrastructureManager.InfrastructureManager.RebootVM") + 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 = self.client.get('/version') + self.assertEqual(res.text, version) + + @patch("IM.InfrastructureManager.InfrastructureManager.CreateDiskSnapshot") + def test_CreateDiskSnapshot(self, CreateDiskSnapshot): + """Test REST StopVM.""" + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} + + CreateDiskSnapshot.return_value = "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 = 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 = 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 = 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 = 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 = 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") + def test_ExportInfrastructure(self, ExportInfrastructure): + """Test REST StopInfrastructure.""" + 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 = self.client.get('/infrastructures/1/data', headers=headers) + self.assertEqual(res.json, {"data": "strinf"}) + + @patch("IM.InfrastructureManager.InfrastructureManager.ImportInfrastructure") + def test_ImportInfrastructure(self, ImportInfrastructure): + """Test REST StopInfrastructure.""" + 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 = 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") + def test_commands(self, check_auth_data, get_infrastructure, SSH): + """Test REST StopInfrastructure.""" + 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 + + res = self.client.get('/infrastructures/1/vms/1/command?step=1', headers=headers) + auth_str = "Authorization: type = InfrastructureManager; username = user; password = pass" + 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" + while [ "$res" == "wait" ] + do + res=`curl --insecure -s -H "%s" -H "Accept: text/plain" %s` + if [ "$res" != "wait" ] + then + echo "$res" > /var/tmp/reverse_ssh.sh + chmod a+x /var/tmp/reverse_ssh.sh + /var/tmp/reverse_ssh.sh + if [ "$res" != "true" ] + then + echo "*/1 * * * * root %s || /var/tmp/reverse_ssh.sh" > /etc/cron.d/reverse_ssh + fi + else + sleep 20 + fi + done""" % (auth_str, url, ps_command) + self.assertEqual(res.text, expected_res) + + inf.auth = Authentication([{'type': 'InfrastructureManager', 'token': 'token'}]) + res = self.client.get('/infrastructures/1/vms/1/command?step=1', headers=headers) + auth_str = "Authorization: type = InfrastructureManager; token = token" + url = "http://localhost/infrastructures/1/vms/1/command?step=2" + expected_res = """ + res="wait" + while [ "$res" == "wait" ] + do + res=`curl --insecure -s -H "%s" -H "Accept: text/plain" %s` + if [ "$res" != "wait" ] + then + echo "$res" > /var/tmp/reverse_ssh.sh + chmod a+x /var/tmp/reverse_ssh.sh + /var/tmp/reverse_ssh.sh + if [ "$res" != "true" ] + then + echo "*/1 * * * * root %s || /var/tmp/reverse_ssh.sh" > /etc/cron.d/reverse_ssh + fi + else + sleep 20 + fi + done""" % (auth_str, url, ps_command) + self.assertEqual(res.text, expected_res) + + radl_master = parse_radl(""" + network publica (outbound = 'yes') + network privada () + + system front ( + cpu.arch='x86_64' and + cpu.count>=1 and + memory.size>=512m and + net_interface.0.ip = '8.8.8.8' and + net_interface.0.connection = 'publica' and + net_interface.1.connection = 'privada' and + disk.0.image.url = 'mock0://linux.for.ev.er' and + disk.0.os.credentials.username = 'ubuntu' and + disk.0.os.credentials.password = 'yoyoyo' and + disk.0.os.name = 'linux' + ) + """) + + radl_vm1 = parse_radl(""" + network privada () + + system wn ( + cpu.arch='x86_64' and + cpu.count>=1 and + memory.size>=512m and + net_interface.0.connection = 'privada' and + disk.0.image.url = 'mock0://linux.for.ev.er' and + disk.0.os.credentials.username = 'ubuntu' and + disk.0.os.credentials.password = 'yoyoyo' and + disk.0.os.name = 'linux' + ) + """) + + radl_vm2 = parse_radl(""" + network privada2 () + + system wn2 ( + cpu.arch='x86_64' and + cpu.count>=1 and + memory.size>=512m and + net_interface.0.connection = 'privada2' and + disk.0.image.url = 'mock0://linux.for.ev.er' and + disk.0.os.credentials.username = 'ubuntu' and + disk.0.os.credentials.password = 'yoyoyo' and + disk.0.os.name = 'linux' + ) + """) + + # in the Master VM + inf.vm_master = VirtualMachine(inf, None, None, radl_master, radl_master) + inf.vm_master.creation_im_id = 0 + ssh = MagicMock() + ssh.test_connectivity.return_value = True + ssh.port = 22 + ssh.private_key = None + ssh.password = "yoyoyo" + ssh.username = "ubuntu" + ssh.host = "8.8.8.8" + SSH.return_value = ssh + vm1 = VirtualMachine(inf, None, None, radl_vm1, radl_vm1) + vm1.creation_im_id = 1 + vm1.destroy = False + vm2 = VirtualMachine(inf, None, None, radl_vm2, radl_vm2) + vm2.creation_im_id = 2 + vm2.destroy = False + inf.vm_list = [inf.vm_master, vm1, vm2] + + res = self.client.get('/infrastructures/1/vms/0/command?step=2', headers=headers) + expected_res = "true" + self.assertEqual(res.text, expected_res) + + res = self.client.get('/infrastructures/1/vms/1/command?step=2', headers=headers) + expected_res = "true" + self.assertEqual(res.text, expected_res) + + # in VM not connected to the Master VM + 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.text, expected_res) + + def test_GetCloudInfo(self): + """Test REST GetCloudInfo.""" + 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): + """Test REST GetCloudInfo with filters.""" + headers = {"AUTHORIZATION": "type = InfrastructureManager; username = user; password = pass"} + + GetCloudImageList.return_value = [] + 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("IM.InfrastructureManager.InfrastructureManager.ChangeInfrastructureAuth") + def test_ChangeInfrastructureAuth(self, ChangeInfrastructureAuth): + """Test REST ChangeInfrastructureAuth.""" + headers = {"AUTHORIZATION": "type = InfrastructureManager; username = user; password = pass"} + ChangeInfrastructureAuth.return_value = None + + 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", + "password": "new_pass"}]) + self.assertEqual(ChangeInfrastructureAuth.call_args_list[0][0][2], True) + + @patch("IM.InfrastructureManager.InfrastructureManager.GetInfrastructureOwners") + def test_GetInfrastructureOwners(self, GetInfrastructureOwners): + """Test REST StopInfrastructure.""" + 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 = self.client.get('/infrastructures/1/authorization', headers=headers) + self.assertEqual(res.text, 'user1\nuser2') + + headers["Accept"] = "application/json" + res = self.client.get('/infrastructures/1/authorization', headers=headers) + self.assertEqual(res.json, {"authorization": ["user1", "user2"]}) + + if __name__ == "__main__": unittest.main() From be4e2cde083a50119743458d6b50e6351b1179c9 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Wed, 5 Jun 2024 16:20:07 +0200 Subject: [PATCH 11/19] Fix tests in vscode --- test/unit/AppDB.py | 6 ++++++ test/unit/SSH.py | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/test/unit/AppDB.py b/test/unit/AppDB.py index 055bfc72..d716ebb7 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/SSH.py b/test/unit/SSH.py index bd4f975a..3dbe2d60 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 From c94dba1b5145ebeac2b775dfca405ef115a759e7 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Wed, 5 Jun 2024 16:25:02 +0200 Subject: [PATCH 12/19] Fix style --- IM/REST.py | 12 ++++---- test/unit/REST.py | 74 ++++++++++++++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/IM/REST.py b/IM/REST.py index 5cd371fa..5eb773ec 100644 --- a/IM/REST.py +++ b/IM/REST.py @@ -72,9 +72,9 @@ def run(host, port): try: server = WSGIServer((host, port), PathInfoDispatcher({'/': app})) if Config.REST_SSL: - server.ssl_adapter = BuiltinSSLAdapter(Config.REST_SSL_CERTFILE, - Config.REST_SSL_KEYFILE, - Config.REST_SSL_CA_CERTS) + server.ssl_adapter = BuiltinSSLAdapter(Config.REST_SSL_CERTFILE, + Config.REST_SSL_KEYFILE, + Config.REST_SSL_CA_CERTS) server.start() except KeyboardInterrupt: @@ -85,7 +85,7 @@ def return_error(code, msg): content_type = get_media_type('Accept') if "application/json" in content_type: - return flask.Response(json.dumps({'message': msg, 'code': code}), status=code, mimetype='application/json') + return flask.Response(json.dumps({'message': msg, 'code': code}), status=code, mimetype='application/json') elif "text/html" in content_type: return flask.Response(HTML_ERROR_TEMPLATE % (code, code, msg), status=code, mimetype='text/html') else: @@ -420,7 +420,7 @@ def RESTGetInfrastructureProperty(infid=None, prop=None): @app.route('/infrastructures', methods=['GET']) def RESTGetInfrastructureList(): try: - auth = get_auth_header() + auth = get_auth_header() except Exception: return return_error(401, "No authentication data provided") @@ -731,7 +731,7 @@ def RESTAddResource(infid=None): extra_headers = {} # If we have to reconfigure the infra, return the ID for the HAProxy stickiness if context: - extra_headers={'InfID': infid} + 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)) diff --git a/test/unit/REST.py b/test/unit/REST.py index 16b3e02f..b4947b19 100755 --- a/test/unit/REST.py +++ b/test/unit/REST.py @@ -85,7 +85,7 @@ 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 = self.client.get('/infrastructures/1', headers=headers) self.assertEqual(200, res.status_code) @@ -213,16 +213,19 @@ def test_CreateInfrastructure(self, CreateInfrastructure): self.assertEqual(res.text, "http://localhost/infrastructures/1") headers["Content-Type"] = "application/json" - res = self.client.post('/infrastructures', headers=headers, data=read_file_as_bytes("../files/test_simple.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") headers["Content-Type"] = "text/yaml" - res = self.client.post('/infrastructures', headers=headers, data=read_file_as_bytes("../files/tosca_simple.yml")) + res = self.client.post('/infrastructures', headers=headers, + data=read_file_as_bytes("../files/tosca_simple.yml")) self.assertEqual(res.text, "http://localhost/infrastructures/1") 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")) + 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}, @@ -230,11 +233,13 @@ def test_CreateInfrastructure(self, CreateInfrastructure): headers["Content-Type"] = "application/json" CreateInfrastructure.side_effect = InvaliddUserException() - res = self.client.post('/infrastructures', headers=headers, data=read_file_as_bytes("../files/test_simple.json")) + 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") CreateInfrastructure.side_effect = UnauthorizedUserException() - res = self.client.post('/infrastructures', headers=headers, data=read_file_as_bytes("../files/test_simple.json")) + 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") @@ -243,7 +248,7 @@ def test_CreateInfrastructureWithErrors(self, CreateInfrastructure): 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"} + "Content-Type": "application/pdf", "Accept": "application/json"} CreateInfrastructure.return_value = "1" @@ -338,24 +343,29 @@ def test_AddResource(self, get_infrastructure, AddResource): self.assertEqual(res.text, "http://localhost/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")) + 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") headers["Content-Type"] = "text/yaml" - res = self.client.post('/infrastructures/1', headers=headers, data=read_file_as_bytes("../files/tosca_simple.yml")) + 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") headers["Content-Type"] = "application/json" AddResource.side_effect = DeletedInfrastructureException() - res = self.client.post('/infrastructures/1', headers=headers, data=read_file_as_bytes("../files/test_simple.json")) + 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.") AddResource.side_effect = IncorrectInfrastructureException() - res = self.client.post('/infrastructures/1', headers=headers, data=read_file_as_bytes("../files/test_simple.json")) + 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.") AddResource.side_effect = UnauthorizedUserException() - res = self.client.post('/infrastructures/1', headers=headers, data=read_file_as_bytes("../files/test_simple.json")) + 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") @@ -403,31 +413,38 @@ def test_AlterVM(self, AlterVM): self.assertEqual(res.text, "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")) + 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") headers["Content-Type"] = "application/json" - res = self.client.put('/infrastructures/1/vms/1', headers=headers, data=read_file_as_bytes("../files/test_simple.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") AlterVM.side_effect = DeletedInfrastructureException() - res = self.client.put('/infrastructures/1/vms/1', headers=headers, data=read_file_as_bytes("../files/test_simple.json")) + 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.") AlterVM.side_effect = IncorrectInfrastructureException() - res = self.client.put('/infrastructures/1/vms/1', headers=headers, data=read_file_as_bytes("../files/test_simple.json")) + 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.") AlterVM.side_effect = UnauthorizedUserException() - res = self.client.put('/infrastructures/1/vms/1', headers=headers, data=read_file_as_bytes("../files/test_simple.json")) + 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.") AlterVM.side_effect = DeletedVMException() - res = self.client.put('/infrastructures/1/vms/1', headers=headers, data=read_file_as_bytes("../files/test_simple.json")) + 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.") AlterVM.side_effect = IncorrectVMException() - res = self.client.put('/infrastructures/1/vms/1', headers=headers, data=read_file_as_bytes("../files/test_simple.json")) + 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") @@ -443,19 +460,24 @@ def test_Reconfigure(self, Reconfigure): self.assertEqual(res.text, "") 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")) + 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, "") Reconfigure.side_effect = DeletedInfrastructureException() - res = self.client.put('/infrastructures/1/reconfigure?vmlist=1,2', headers=headers, data=read_file_as_bytes("../files/test_simple.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, "Error reconfiguring infrastructure: Deleted infrastructure.") Reconfigure.side_effect = IncorrectInfrastructureException() - 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.") + 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.")) Reconfigure.side_effect = UnauthorizedUserException() - res = self.client.put('/infrastructures/1/reconfigure?vmlist=1,2', headers=headers, data=read_file_as_bytes("../files/test_simple.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, "Error reconfiguring infrastructure: Access to this infrastructure not granted.") @patch("IM.InfrastructureManager.InfrastructureManager.StartInfrastructure") @@ -786,7 +808,7 @@ def test_ChangeInfrastructureAuth(self, ChangeInfrastructureAuth): 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", @@ -794,7 +816,7 @@ def test_ChangeInfrastructureAuth(self, ChangeInfrastructureAuth): self.assertEqual(ChangeInfrastructureAuth.call_args_list[0][0][2], True) @patch("IM.InfrastructureManager.InfrastructureManager.GetInfrastructureOwners") - def test_GetInfrastructureOwners(self, GetInfrastructureOwners): + def test_GetInfrastructureOwners(self, GetInfrastructureOwners): """Test REST StopInfrastructure.""" headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " From 787383fc96d5fcb1d0b52c1a1e1e864c6e62cea6 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Wed, 5 Jun 2024 16:35:47 +0200 Subject: [PATCH 13/19] Fix issues --- IM/REST.py | 8 +++++--- test/unit/REST.py | 7 +++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/IM/REST.py b/IM/REST.py index 5eb773ec..40787026 100644 --- a/IM/REST.py +++ b/IM/REST.py @@ -191,7 +191,7 @@ 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, extra_headers={}): +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 """ @@ -234,7 +234,8 @@ def format_output(res, default_type="text/plain", field_name=None, list_field_na if content_type: headers = {'Content-Type': content_type} - headers.update(extra_headers) + if extra_headers: + headers.update(extra_headers) return flask.make_response(info, 200, headers) else: return return_error(415, "Unsupported Accept Media Types: %s" % ",".join(accept)) @@ -247,7 +248,8 @@ def format_output(res, default_type="text/plain", field_name=None, list_field_na else: info = "%s" % res headers = {'Content-Type': default_type} - headers.update(extra_headers) + if extra_headers: + headers.update(extra_headers) return flask.make_response(info, 200, {'Content-Type': default_type}) diff --git a/test/unit/REST.py b/test/unit/REST.py index b4947b19..81630f5d 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,14 +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 app, return_error, format_output +from IM.REST import app def read_file_as_bytes(file_name): @@ -199,7 +197,8 @@ def test_DestroyInfrastructure(self, DestroyInfrastructure): self.assertEqual(res.text, "Error Destroying Inf: Invalid State to perform this operation.") @patch("IM.InfrastructureManager.InfrastructureManager.CreateInfrastructure") - def test_CreateInfrastructure(self, CreateInfrastructure): + @patch("IM.InfrastructureManager.InfrastructureManager.get_infrastructure") + def test_CreateInfrastructure(self, get_infrastructure, CreateInfrastructure): """Test REST CreateInfrastructure.""" headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" "id = one; type = OpenNebula; host = ramses.i3m.upv.es:2633; " From b4019fb0386784d2d05c0c9e849f27f054d9b94d Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Thu, 6 Jun 2024 08:19:34 +0200 Subject: [PATCH 14/19] Update docs --- README.md | 19 +++++++++---------- doc/source/manual.rst | 13 +++++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index ef7470de..c424ccfd 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/doc/source/manual.rst b/doc/source/manual.rst index aa13e584..bf9a6d76 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 From 9ff5074e89746417dba9bc8aa63f9f747882a286 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Thu, 6 Jun 2024 08:21:33 +0200 Subject: [PATCH 15/19] Remove obsolete packages generators --- packages/generate_deb.sh | 12 ------------ packages/generate_rpm.sh | 7 ------- 2 files changed, 19 deletions(-) delete mode 100755 packages/generate_deb.sh delete mode 100755 packages/generate_rpm.sh diff --git a/packages/generate_deb.sh b/packages/generate_deb.sh deleted file mode 100755 index 6daad14e..00000000 --- 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 a2cc6790..00000000 --- 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 From 68c042299a5ef5718f21e18c8449729d53fb1216 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Thu, 6 Jun 2024 08:47:38 +0200 Subject: [PATCH 16/19] Improve code --- IM/REST.py | 112 +++++++++++++++++++++----------------------------- im_service.py | 8 ++-- 2 files changed, 51 insertions(+), 69 deletions(-) diff --git a/IM/REST.py b/IM/REST.py index 40787026..7e289458 100644 --- a/IM/REST.py +++ b/IM/REST.py @@ -55,7 +55,6 @@ """ -REST_URL = None 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) @@ -69,16 +68,13 @@ def run_in_thread(host, port): def run(host, port): - try: - server = WSGIServer((host, port), PathInfoDispatcher({'/': app})) - if Config.REST_SSL: - server.ssl_adapter = BuiltinSSLAdapter(Config.REST_SSL_CERTFILE, - Config.REST_SSL_KEYFILE, - Config.REST_SSL_CA_CERTS) - - server.start() - except KeyboardInterrupt: - server.stop() + global flask_server + flask_server = WSGIServer((host, port), PathInfoDispatcher({'/': app})) + if Config.REST_SSL: + 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): @@ -93,7 +89,8 @@ def return_error(code, msg): def stop(): - pass + logger.info('Stopping REST API server...') + flask_server.stop() def get_media_type(header): @@ -122,11 +119,6 @@ def get_auth_header(): Get the Authentication object from the AUTHORIZATION header replacing the new line chars. """ - # Initialize REST_URL - global REST_URL - if REST_URL is None: - REST_URL = flask.request.url_root - auth_header = flask.request.headers['AUTHORIZATION'] user_pass = None @@ -197,60 +189,50 @@ def format_output(res, default_type="text/plain", field_name=None, list_field_na """ 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 + 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: - return return_error(415, "Unsupported Accept Media Types: %s" % ",".join(accept)) - 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 - headers = {'Content-Type': default_type} + if content_type: + headers = {'Content-Type': content_type} if extra_headers: headers.update(extra_headers) - return flask.make_response(info, 200, {'Content-Type': default_type}) + return flask.make_response(info, 200, headers) + else: + return return_error(415, "Unsupported Accept Media Types: %s" % ",".join(accept)) @app.after_request diff --git a/im_service.py b/im_service.py index a242e56e..98c3ee38 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") From 69baddc1c8afa069aefc71a3744a8fdee8f50212 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Thu, 6 Jun 2024 09:03:56 +0200 Subject: [PATCH 17/19] Improve tests --- test/unit/REST.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/unit/REST.py b/test/unit/REST.py index 81630f5d..2be21635 100755 --- a/test/unit/REST.py +++ b/test/unit/REST.py @@ -207,6 +207,7 @@ def test_CreateInfrastructure(self, get_infrastructure, CreateInfrastructure): 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") res = self.client.post('/infrastructures?async=yes', headers=headers, data=BytesIO(b"radl")) self.assertEqual(res.text, "http://localhost/infrastructures/1") @@ -340,6 +341,7 @@ def test_AddResource(self, get_infrastructure, AddResource): 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") headers["Content-Type"] = "application/json" res = self.client.post('/infrastructures/1', headers=headers, @@ -457,6 +459,7 @@ def test_Reconfigure(self, Reconfigure): 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") headers["Content-Type"] = "application/json" res = self.client.put('/infrastructures/1/reconfigure?vmlist=1,2', From 1cab3ab4ace1469dc125bce405bb9d8187ab64bb Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Thu, 6 Jun 2024 09:05:56 +0200 Subject: [PATCH 18/19] Fix style --- test/unit/REST.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/REST.py b/test/unit/REST.py index 2be21635..48f7a168 100755 --- a/test/unit/REST.py +++ b/test/unit/REST.py @@ -207,7 +207,7 @@ def test_CreateInfrastructure(self, get_infrastructure, CreateInfrastructure): 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") + self.assertEqual(res.headers['InfID'], "1") res = self.client.post('/infrastructures?async=yes', headers=headers, data=BytesIO(b"radl")) self.assertEqual(res.text, "http://localhost/infrastructures/1") @@ -341,7 +341,7 @@ def test_AddResource(self, get_infrastructure, AddResource): 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") + self.assertEqual(res.headers['InfID'], "1") headers["Content-Type"] = "application/json" res = self.client.post('/infrastructures/1', headers=headers, @@ -459,7 +459,7 @@ def test_Reconfigure(self, Reconfigure): 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") + self.assertEqual(res.headers['InfID'], "1") headers["Content-Type"] = "application/json" res = self.client.put('/infrastructures/1/reconfigure?vmlist=1,2', From 1ef4b5b746f4b933ff9f2fe160ead6f27d556dcd Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Thu, 6 Jun 2024 09:19:07 +0200 Subject: [PATCH 19/19] Revert change of REST_URL --- IM/REST.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/IM/REST.py b/IM/REST.py index 7e289458..f04f8853 100644 --- a/IM/REST.py +++ b/IM/REST.py @@ -55,7 +55,7 @@ """ - +REST_URL = None 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 @@ -119,6 +119,11 @@ def get_auth_header(): Get the Authentication object from the AUTHORIZATION header replacing the new line chars. """ + # Initialize REST_URL + global REST_URL + if REST_URL is None: + REST_URL = flask.request.url_root + auth_header = flask.request.headers['AUTHORIZATION'] user_pass = None