X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=blobdiff_plain;f=osm_nbi%2Fnbi.py;h=fd2b3e8fd30ca0defc7af80a6d08174e74b45fd0;hp=8f995f5c748a6ee5d83da80efc392d23bead16e4;hb=02e700c9eceb147f4c72c8fe18975bfaac6ec759;hpb=b19cadcf9ff2169f34f6db79b866bad91f9c8aed diff --git a/osm_nbi/nbi.py b/osm_nbi/nbi.py index 8f995f5..fd2b3e8 100644 --- a/osm_nbi/nbi.py +++ b/osm_nbi/nbi.py @@ -47,7 +47,6 @@ auth_database_version = '1.0' nbi_server = None # instance of Server class subscription_thread = None # instance of SubscriptionThread class - """ North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented) URL: /osm GET POST PUT DELETE PATCH @@ -112,6 +111,12 @@ URL: /osm GET POST / O O O /sdns O O / O O O + /k8sclusters O O + / O O O + /k8srepos O O + / O O + /osmrepos O O + / O O /nst/v1 O O /netslice_templates_content O O @@ -212,45 +217,66 @@ valid_url_methods = { }, "users": {"METHODS": ("GET", "POST"), "ROLE_PERMISSION": "users:", - "": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"), + "": {"METHODS": ("GET", "DELETE", "PATCH"), "ROLE_PERMISSION": "users:id:" } }, "projects": {"METHODS": ("GET", "POST"), "ROLE_PERMISSION": "projects:", - "": {"METHODS": ("GET", "DELETE", "PUT"), + "": {"METHODS": ("GET", "DELETE", "PATCH"), "ROLE_PERMISSION": "projects:id:"} }, "roles": {"METHODS": ("GET", "POST"), "ROLE_PERMISSION": "roles:", - "": {"METHODS": ("GET", "POST", "DELETE", "PUT"), + "": {"METHODS": ("GET", "DELETE", "PATCH"), "ROLE_PERMISSION": "roles:id:" } }, "vims": {"METHODS": ("GET", "POST"), "ROLE_PERMISSION": "vims:", - "": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"), + "": {"METHODS": ("GET", "DELETE", "PATCH"), "ROLE_PERMISSION": "vims:id:" } }, "vim_accounts": {"METHODS": ("GET", "POST"), "ROLE_PERMISSION": "vim_accounts:", - "": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"), + "": {"METHODS": ("GET", "DELETE", "PATCH"), "ROLE_PERMISSION": "vim_accounts:id:" } }, "wim_accounts": {"METHODS": ("GET", "POST"), "ROLE_PERMISSION": "wim_accounts:", - "": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"), + "": {"METHODS": ("GET", "DELETE", "PATCH"), "ROLE_PERMISSION": "wim_accounts:id:" } }, "sdns": {"METHODS": ("GET", "POST"), "ROLE_PERMISSION": "sdn_controllers:", - "": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"), + "": {"METHODS": ("GET", "DELETE", "PATCH"), "ROLE_PERMISSION": "sdn_controllers:id:" } }, + "k8sclusters": {"METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "k8sclusters:", + "": {"METHODS": ("GET", "DELETE", "PATCH"), + "ROLE_PERMISSION": "k8sclusters:id:" + } + }, + "k8srepos": {"METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "k8srepos:", + "": {"METHODS": ("GET", "DELETE"), + "ROLE_PERMISSION": "k8srepos:id:" + } + }, + "osmrepos": {"METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "osmrepos:", + "": {"METHODS": ("GET", "DELETE", "PATCH"), + "ROLE_PERMISSION": "osmrepos:id:" + } + }, + "domains": {"METHODS": ("GET", ), + "ROLE_PERMISSION": "domains:", + }, } }, "pdu": { @@ -281,9 +307,9 @@ valid_url_methods = { "nsd": {"METHODS": ("GET",), # descriptor inside package "ROLE_PERMISSION": "nsds:id:content:" }, - "artifacts": {"*": {"METHODS": ("GET",), - "ROLE_PERMISSION": "nsds:id:nsd_artifact:" - } + "artifacts": {"METHODS": ("GET",), + "ROLE_PERMISSION": "nsds:id:nsd_artifact:", + "*": None, } } }, @@ -318,15 +344,24 @@ valid_url_methods = { "vnfd": {"METHODS": ("GET", ), # descriptor inside package "ROLE_PERMISSION": "vnfds:id:content:" }, - "artifacts": {"*": {"METHODS": ("GET", ), - "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:" - } - } + "artifacts": {"METHODS": ("GET", ), + "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:", + "*": None, + }, + "action": {"METHODS": ("POST", ), + "ROLE_PERMISSION": "vnfds:id:action:" + }, } }, "subscriptions": {"TODO": ("GET", "POST"), "": {"TODO": ("GET", "DELETE")} }, + "vnfpkg_op_occs": {"METHODS": ("GET", ), + "ROLE_PERMISSION": "vnfds:vnfpkgops:", + "": {"METHODS": ("GET", ), + "ROLE_PERMISSION": "vnfds:vnfpkgops:id:" + } + }, } }, "nslcm": { @@ -373,6 +408,12 @@ valid_url_methods = { "ROLE_PERMISSION": "vnf_instances:id:" } }, + "subscriptions": {"METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "ns_subscriptions:", + "": {"METHODS": ("GET", "DELETE"), + "ROLE_PERMISSION": "ns_subscriptions:id:" + } + }, } }, "nst": { @@ -393,9 +434,9 @@ valid_url_methods = { "nst": {"METHODS": ("GET",), # descriptor inside package "ROLE_PERMISSION": "slice_templates:id:content:" }, - "artifacts": {"*": {"METHODS": ("GET",), - "ROLE_PERMISSION": "slice_templates:id:content:" - } + "artifacts": {"METHODS": ("GET",), + "ROLE_PERMISSION": "slice_templates:id:content:", + "*": None } } }, @@ -465,8 +506,8 @@ class Server(object): def __init__(self): self.instance += 1 - self.engine = Engine() self.authenticator = Authenticator(valid_url_methods, valid_query_string) + self.engine = Engine(self.authenticator) def _format_in(self, kwargs): try: @@ -576,20 +617,14 @@ class Server(object): # TODO check that cherrypy close file. If not implement pending things to close per thread next return data if accept: - if "application/json" in accept: - cherrypy.response.headers["Content-Type"] = 'application/json; charset=utf-8' - a = json.dumps(data, indent=4) + "\n" - return a.encode("utf8") - elif "text/html" in accept: + if "text/html" in accept: return html.format(data, cherrypy.request, cherrypy.response, token_info) - elif "application/yaml" in accept or "*/*" in accept or "text/plain" in accept: pass - # if there is not any valid accept, raise an error. But if response is already an error, format in yaml - elif cherrypy.response.status >= 400: - raise cherrypy.HTTPError(HTTPStatus.NOT_ACCEPTABLE.value, - "Only 'Accept' of type 'application/json' or 'application/yaml' " - "for output format are available") + elif "application/json" in accept or (cherrypy.response.status and cherrypy.response.status >= 300): + cherrypy.response.headers["Content-Type"] = 'application/json; charset=utf-8' + a = json.dumps(data, indent=4) + "\n" + return a.encode("utf8") cherrypy.response.headers["Content-Type"] = 'application/yaml' return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False, encoding='utf-8', allow_unicode=True) # , canonical=True, default_style='"' @@ -632,6 +667,21 @@ class Server(object): } return self._format_out(problem_details, None) + def domain(self): + try: + domains = { + "user_domain_name": cherrypy.tree.apps['/osm'].config["authentication"].get("user_domain_name"), + "project_domain_name": cherrypy.tree.apps['/osm'].config["authentication"].get("project_domain_name")} + return self._format_out(domains) + except NbiException as e: + cherrypy.response.status = e.http_code.value + problem_details = { + "code": e.http_code.name, + "status": e.http_code.value, + "detail": str(e), + } + return self._format_out(problem_details, None) + @staticmethod def _format_login(token_info): """ @@ -698,6 +748,10 @@ class Server(object): @cherrypy.expose def test(self, *args, **kwargs): + if not cherrypy.config.get("server.enable_test") or (isinstance(cherrypy.config["server.enable_test"], str) and + cherrypy.config["server.enable_test"].lower() == "false"): + cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value + return "test URL is disabled" thread_info = None if args and args[0] == "help": return "
\ninit\nfile/  download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
@@ -761,8 +815,9 @@ class Server(object):
                         return_text += "  {}: {}\n".format(k, v)
                 elif cherrypy.request.method == 'GET':
                     for k, v in kwargs.items():
-                        self.engine.msg.write(main_topic, k, yaml.load(v), Loader=yaml.SafeLoader)
-                        return_text += "  {}: {}\n".format(k, yaml.load(v), Loader=yaml.SafeLoader)
+                        v_dict = yaml.load(v, Loader=yaml.SafeLoader)
+                        self.engine.msg.write(main_topic, k, v_dict)
+                        return_text += "  {}: {}\n".format(k, v_dict)
             except Exception as e:
                 return_text += "Error: " + str(e)
             return_text += "
\n" @@ -806,7 +861,9 @@ class Server(object): elif "" in reference: reference = reference[""] elif "*" in reference: - reference = reference["*"] + # if there is content + if reference["*"]: + reference = reference["*"] break else: raise NbiException("Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED) @@ -946,11 +1003,12 @@ class Server(object): if main_topic == "admin" and topic == "tokens": return self.token(method, _id, kwargs) token_info = self.authenticator.authorize(role_permission, query_string_operations, _id) + if main_topic == "admin" and topic == "domains": + return self.domain() engine_session = self._manage_admin_query(token_info, kwargs, method, _id) indata = self._format_in(kwargs) engine_topic = topic - if topic == "subscriptions": - engine_topic = main_topic + "_" + topic + if item and topic != "pm_jobs": engine_topic = item @@ -958,6 +1016,10 @@ class Server(object): engine_topic = "nsds" elif main_topic == "vnfpkgm": engine_topic = "vnfds" + if topic == "vnfpkg_op_occs": + engine_topic = "vnfpkgops" + if topic == "vnf_packages" and item == "action": + engine_topic = "vnfpkgops" elif main_topic == "nslcm": engine_topic = "nsrs" if topic == "ns_lcm_op_occs": @@ -975,6 +1037,9 @@ class Server(object): if engine_topic == "vims": # TODO this is for backward compatibility, it will be removed in the future engine_topic = "vim_accounts" + if topic == "subscriptions": + engine_topic = main_topic + "_" + topic + if method == "GET": if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"): if item in ("vnfd", "nsd", "nst"): @@ -995,6 +1060,7 @@ class Server(object): # TODO check that project_id (_id in this context) has permissions _id = args[0] outdata = self.engine.get_item(engine_session, engine_topic, _id) + elif method == "POST": cherrypy.response.status = HTTPStatus.CREATED.value if topic in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"): @@ -1033,7 +1099,6 @@ class Server(object): indata["netsliceInstanceId"] = _id nsilcmop_id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", indata, kwargs) outdata = {"id": _id, "nsilcmop_id": nsilcmop_id} - elif topic == "netslice_instances" and item: indata["lcmOperationType"] = item indata["netsliceInstanceId"] = _id @@ -1041,6 +1106,21 @@ class Server(object): self._set_location_header(main_topic, version, "nsi_lcm_op_occs", _id) outdata = {"id": _id} cherrypy.response.status = HTTPStatus.ACCEPTED.value + elif topic == "vnf_packages" and item == "action": + indata["lcmOperationType"] = item + indata["vnfPkgId"] = _id + _id, _ = self.engine.new_item(rollback, engine_session, "vnfpkgops", indata, kwargs) + self._set_location_header(main_topic, version, "vnfpkg_op_occs", _id) + outdata = {"id": _id} + cherrypy.response.status = HTTPStatus.ACCEPTED.value + elif topic == "subscriptions": + _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs) + self._set_location_header(main_topic, version, topic, _id) + link = {} + link["self"] = cherrypy.response.headers["Location"] + outdata = {"id": _id, "filter": indata["filter"], "callbackUri": indata["CallbackUri"], + "_links": link} + cherrypy.response.status = HTTPStatus.CREATED.value else: _id, op_id = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs, cherrypy.request.headers) @@ -1056,34 +1136,32 @@ class Server(object): outdata = self.engine.del_item_list(engine_session, engine_topic, kwargs) cherrypy.response.status = HTTPStatus.OK.value else: # len(args) > 1 - delete_in_process = False + # for NS NSI generate an operation + op_id = None if topic == "ns_instances_content" and not engine_session["force"]: nslcmop_desc = { "lcmOperationType": "terminate", "nsInstanceId": _id, "autoremove": True } - opp_id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", nslcmop_desc, None) - if opp_id: - delete_in_process = True - outdata = {"_id": opp_id} - cherrypy.response.status = HTTPStatus.ACCEPTED.value + op_id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", nslcmop_desc, kwargs) + if op_id: + outdata = {"_id": op_id} elif topic == "netslice_instances_content" and not engine_session["force"]: nsilcmop_desc = { "lcmOperationType": "terminate", "netsliceInstanceId": _id, "autoremove": True } - opp_id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", nsilcmop_desc, None) - if opp_id: - delete_in_process = True - outdata = {"_id": opp_id} - cherrypy.response.status = HTTPStatus.ACCEPTED.value - if not delete_in_process: - self.engine.del_item(engine_session, engine_topic, _id) - cherrypy.response.status = HTTPStatus.NO_CONTENT.value - if engine_topic in ("vim_accounts", "wim_accounts", "sdns"): - cherrypy.response.status = HTTPStatus.ACCEPTED.value + op_id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", nsilcmop_desc, None) + if op_id: + outdata = {"_id": op_id} + # if there is not any deletion in process, delete + if not op_id: + op_id = self.engine.del_item(engine_session, engine_topic, _id) + if op_id: + outdata = {"op_id": op_id} + cherrypy.response.status = HTTPStatus.ACCEPTED.value if op_id else HTTPStatus.NO_CONTENT.value elif method in ("PUT", "PATCH"): op_id = None @@ -1110,6 +1188,11 @@ class Server(object): # if Role information changes, it is needed to reload the information of roles if topic == "roles" and method != "GET": self.authenticator.load_operation_to_allowed_roles() + + if topic == "projects" and method == "DELETE" \ + or topic in ["users", "roles"] and method in ["PUT", "PATCH", "DELETE"]: + self.authenticator.remove_token_from_cache() + return self._format_out(outdata, token_info, _format) except Exception as e: if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException, @@ -1130,6 +1213,9 @@ class Server(object): if rollback_item.get("operation") == "set": self.engine.db.set_one(rollback_item["topic"], {"_id": rollback_item["_id"]}, rollback_item["content"], fail_on_empty=False) + elif rollback_item.get("operation") == "del_list": + self.engine.db.del_list(rollback_item["topic"], rollback_item["filter"], + fail_on_empty=False) else: self.engine.db.del_one(rollback_item["topic"], {"_id": rollback_item["_id"]}, fail_on_empty=False) @@ -1254,14 +1340,9 @@ def _start_service(): subscription_thread.start() # Do not capture except SubscriptionException - # load and print version. Ignore possible errors, e.g. file not found - try: - backend = engine_config["authentication"]["backend"] - nbi_version - cherrypy.log.error("Starting OSM NBI Version '{}' with '{}' authentication backend" - .format(nbi_version + " " + nbi_version_date, backend)) - except Exception: - pass + backend = engine_config["authentication"]["backend"] + cherrypy.log.error("Starting OSM NBI Version '{} {}' with '{}' authentication backend" + .format(nbi_version, nbi_version_date, backend)) def _stop_service():