X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=blobdiff_plain;f=osm_nbi%2Fnbi.py;h=d5a81ce46f8b8993bdec1daaf92bfda96f43c21d;hp=33147b73e836875f5f1449cc69c2418d8b82dc51;hb=5758955b7b394517ff5caf5506a4400cdc5aa372;hpb=01b15d3166ea28266fb3d994d0615e4091c43c08 diff --git a/osm_nbi/nbi.py b/osm_nbi/nbi.py index 33147b7..d5a81ce 100644 --- a/osm_nbi/nbi.py +++ b/osm_nbi/nbi.py @@ -18,33 +18,34 @@ import cherrypy import time import json import yaml -import html_out as html +import osm_nbi.html_out as html import logging import logging.handlers import getopt import sys -from authconn import AuthException, AuthconnException -from auth import Authenticator -from engine import Engine, EngineException -from subscriptions import SubscriptionThread -from validation import ValidationError +from osm_nbi.authconn import AuthException, AuthconnException +from osm_nbi.auth import Authenticator +from osm_nbi.engine import Engine, EngineException +from osm_nbi.subscriptions import SubscriptionThread +from osm_nbi.validation import ValidationError from osm_common.dbbase import DbException from osm_common.fsbase import FsException from osm_common.msgbase import MsgException from http import HTTPStatus from codecs import getreader from os import environ, path +from osm_nbi import version as nbi_version, version_date as nbi_version_date __author__ = "Alfonso Tierno " -__version__ = "0.1.3" -version_date = "Jan 2019" -database_version = '1.2' -auth_database_version = '1.0' -nbi_server = None # instance of Server class -subscription_thread = None # instance of SubscriptionThread class +__version__ = "0.1.3" # file version, not NBI version +version_date = "Aug 2019" +database_version = "1.2" +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) @@ -110,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 @@ -202,235 +209,350 @@ valid_url_methods = { # contains allowed URL and methods, and the role_permission name "admin": { "v1": { - "tokens": {"METHODS": ("GET", "POST", "DELETE"), - "ROLE_PERMISSION": "tokens:", - "": {"METHODS": ("GET", "DELETE"), - "ROLE_PERMISSION": "tokens:id:" - } - }, - "users": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "users:", - "": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"), - "ROLE_PERMISSION": "users:id:" - } - }, - "projects": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "projects:", - "": {"METHODS": ("GET", "DELETE", "PUT"), - "ROLE_PERMISSION": "projects:id:"} - }, - "roles": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "roles:", - "": {"METHODS": ("GET", "POST", "DELETE", "PUT"), - "ROLE_PERMISSION": "roles:id:" - } - }, - "vims": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "vims:", - "": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"), - "ROLE_PERMISSION": "vims:id:" - } - }, - "vim_accounts": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "vim_accounts:", - "": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"), - "ROLE_PERMISSION": "vim_accounts:id:" - } - }, - "wim_accounts": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "wim_accounts:", - "": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"), - "ROLE_PERMISSION": "wim_accounts:id:" - } - }, - "sdns": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "sdn_controllers:", - "": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"), - "ROLE_PERMISSION": "sdn_controllers:id:" - } - }, + "tokens": { + "METHODS": ("GET", "POST", "DELETE"), + "ROLE_PERMISSION": "tokens:", + "": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"}, + }, + "users": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "users:", + "": { + "METHODS": ("GET", "DELETE", "PATCH"), + "ROLE_PERMISSION": "users:id:", + }, + }, + "projects": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "projects:", + "": { + "METHODS": ("GET", "DELETE", "PATCH"), + "ROLE_PERMISSION": "projects:id:", + }, + }, + "roles": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "roles:", + "": { + "METHODS": ("GET", "DELETE", "PATCH"), + "ROLE_PERMISSION": "roles:id:", + }, + }, + "vims": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "vims:", + "": { + "METHODS": ("GET", "DELETE", "PATCH"), + "ROLE_PERMISSION": "vims:id:", + }, + }, + "vim_accounts": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "vim_accounts:", + "": { + "METHODS": ("GET", "DELETE", "PATCH"), + "ROLE_PERMISSION": "vim_accounts:id:", + }, + }, + "wim_accounts": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "wim_accounts:", + "": { + "METHODS": ("GET", "DELETE", "PATCH"), + "ROLE_PERMISSION": "wim_accounts:id:", + }, + }, + "sdns": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "sdn_controllers:", + "": { + "METHODS": ("GET", "DELETE", "PATCH"), + "ROLE_PERMISSION": "sdn_controllers:id:", + }, + }, + "k8sclusters": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "k8sclusters:", + "": { + "METHODS": ("GET", "DELETE", "PATCH"), + "ROLE_PERMISSION": "k8sclusters:id:", + }, + }, + "vca": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "vca:", + "": { + "METHODS": ("GET", "DELETE", "PATCH"), + "ROLE_PERMISSION": "vca: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": { "v1": { - "pdu_descriptors": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "pduds:", - "": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"), - "ROLE_PERMISSION": "pduds:id:" - } - }, + "pdu_descriptors": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "pduds:", + "": { + "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"), + "ROLE_PERMISSION": "pduds:id:", + }, + }, } }, "nsd": { "v1": { - "ns_descriptors_content": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "nsds:", - "": {"METHODS": ("GET", "PUT", "DELETE"), - "ROLE_PERMISSION": "nsds:id:" - } - }, - "ns_descriptors": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "nsds:", - "": {"METHODS": ("GET", "DELETE", "PATCH"), - "ROLE_PERMISSION": "nsds:id:", - "nsd_content": {"METHODS": ("GET", "PUT"), - "ROLE_PERMISSION": "nsds:id:content:", - }, - "nsd": {"METHODS": ("GET",), # descriptor inside package - "ROLE_PERMISSION": "nsds:id:content:" - }, - "artifacts": {"*": {"METHODS": ("GET",), - "ROLE_PERMISSION": "nsds:id:nsd_artifact:" - } - } - } - }, - "pnf_descriptors": {"TODO": ("GET", "POST"), - "": {"TODO": ("GET", "DELETE", "PATCH"), - "pnfd_content": {"TODO": ("GET", "PUT")} - } - }, - "subscriptions": {"TODO": ("GET", "POST"), - "": {"TODO": ("GET", "DELETE")} - }, + "ns_descriptors_content": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "nsds:", + "": { + "METHODS": ("GET", "PUT", "DELETE"), + "ROLE_PERMISSION": "nsds:id:", + }, + }, + "ns_descriptors": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "nsds:", + "": { + "METHODS": ("GET", "DELETE", "PATCH"), + "ROLE_PERMISSION": "nsds:id:", + "nsd_content": { + "METHODS": ("GET", "PUT"), + "ROLE_PERMISSION": "nsds:id:content:", + }, + "nsd": { + "METHODS": ("GET",), # descriptor inside package + "ROLE_PERMISSION": "nsds:id:content:", + }, + "artifacts": { + "METHODS": ("GET",), + "ROLE_PERMISSION": "nsds:id:nsd_artifact:", + "*": None, + }, + }, + }, + "pnf_descriptors": { + "TODO": ("GET", "POST"), + "": { + "TODO": ("GET", "DELETE", "PATCH"), + "pnfd_content": {"TODO": ("GET", "PUT")}, + }, + }, + "subscriptions": { + "TODO": ("GET", "POST"), + "": {"TODO": ("GET", "DELETE")}, + }, } }, "vnfpkgm": { "v1": { - "vnf_packages_content": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "vnfds:", - "": {"METHODS": ("GET", "PUT", "DELETE"), - "ROLE_PERMISSION": "vnfds:id:"} - }, - "vnf_packages": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "vnfds:", - "": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo - "ROLE_PERMISSION": "vnfds:id:", - "package_content": {"METHODS": ("GET", "PUT"), # package - "ROLE_PERMISSION": "vnfds:id:", - "upload_from_uri": {"METHODS": (), - "TODO": ("POST", ), - "ROLE_PERMISSION": "vnfds:id:upload:" - } - }, - "vnfd": {"METHODS": ("GET", ), # descriptor inside package - "ROLE_PERMISSION": "vnfds:id:content:" - }, - "artifacts": {"*": {"METHODS": ("GET", ), - "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:" - } - } - } - }, - "subscriptions": {"TODO": ("GET", "POST"), - "": {"TODO": ("GET", "DELETE")} - }, + "vnf_packages_content": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "vnfds:", + "": { + "METHODS": ("GET", "PUT", "DELETE"), + "ROLE_PERMISSION": "vnfds:id:", + }, + }, + "vnf_packages": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "vnfds:", + "": { + "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo + "ROLE_PERMISSION": "vnfds:id:", + "package_content": { + "METHODS": ("GET", "PUT"), # package + "ROLE_PERMISSION": "vnfds:id:", + "upload_from_uri": { + "METHODS": (), + "TODO": ("POST",), + "ROLE_PERMISSION": "vnfds:id:upload:", + }, + }, + "vnfd": { + "METHODS": ("GET",), # descriptor inside package + "ROLE_PERMISSION": "vnfds:id:content:", + }, + "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": { "v1": { - "ns_instances_content": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "ns_instances:", - "": {"METHODS": ("GET", "DELETE"), - "ROLE_PERMISSION": "ns_instances:id:" - } - }, - "ns_instances": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "ns_instances:", - "": {"METHODS": ("GET", "DELETE"), - "ROLE_PERMISSION": "ns_instances:id:", - "scale": {"METHODS": ("POST",), - "ROLE_PERMISSION": "ns_instances:id:scale:" - }, - "terminate": {"METHODS": ("POST",), - "ROLE_PERMISSION": "ns_instances:id:terminate:" - }, - "instantiate": {"METHODS": ("POST",), - "ROLE_PERMISSION": "ns_instances:id:instantiate:" - }, - "action": {"METHODS": ("POST",), - "ROLE_PERMISSION": "ns_instances:id:action:" - }, - } - }, - "ns_lcm_op_occs": {"METHODS": ("GET",), - "ROLE_PERMISSION": "ns_instances:opps:", - "": {"METHODS": ("GET",), - "ROLE_PERMISSION": "ns_instances:opps:id:" - }, - }, - "vnfrs": {"METHODS": ("GET",), - "ROLE_PERMISSION": "vnf_instances:", - "": {"METHODS": ("GET",), - "ROLE_PERMISSION": "vnf_instances:id:" - } - }, - "vnf_instances": {"METHODS": ("GET",), - "ROLE_PERMISSION": "vnf_instances:", - "": {"METHODS": ("GET",), - "ROLE_PERMISSION": "vnf_instances:id:" - } - }, + "ns_instances_content": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "ns_instances:", + "": { + "METHODS": ("GET", "DELETE"), + "ROLE_PERMISSION": "ns_instances:id:", + }, + }, + "ns_instances": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "ns_instances:", + "": { + "METHODS": ("GET", "DELETE"), + "ROLE_PERMISSION": "ns_instances:id:", + "scale": { + "METHODS": ("POST",), + "ROLE_PERMISSION": "ns_instances:id:scale:", + }, + "terminate": { + "METHODS": ("POST",), + "ROLE_PERMISSION": "ns_instances:id:terminate:", + }, + "instantiate": { + "METHODS": ("POST",), + "ROLE_PERMISSION": "ns_instances:id:instantiate:", + }, + "action": { + "METHODS": ("POST",), + "ROLE_PERMISSION": "ns_instances:id:action:", + }, + }, + }, + "ns_lcm_op_occs": { + "METHODS": ("GET",), + "ROLE_PERMISSION": "ns_instances:opps:", + "": { + "METHODS": ("GET",), + "ROLE_PERMISSION": "ns_instances:opps:id:", + }, + }, + "vnfrs": { + "METHODS": ("GET",), + "ROLE_PERMISSION": "vnf_instances:", + "": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"}, + }, + "vnf_instances": { + "METHODS": ("GET",), + "ROLE_PERMISSION": "vnf_instances:", + "": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"}, + }, + "subscriptions": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "ns_subscriptions:", + "": { + "METHODS": ("GET", "DELETE"), + "ROLE_PERMISSION": "ns_subscriptions:id:", + }, + }, } }, "nst": { "v1": { - "netslice_templates_content": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "slice_templates:", - "": {"METHODS": ("GET", "PUT", "DELETE"), - "ROLE_PERMISSION": "slice_templates:id:", } - }, - "netslice_templates": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "slice_templates:", - "": {"METHODS": ("GET", "DELETE"), - "TODO": ("PATCH",), - "ROLE_PERMISSION": "slice_templates:id:", - "nst_content": {"METHODS": ("GET", "PUT"), - "ROLE_PERMISSION": "slice_templates:id:content:" - }, - "nst": {"METHODS": ("GET",), # descriptor inside package - "ROLE_PERMISSION": "slice_templates:id:content:" - }, - "artifacts": {"*": {"METHODS": ("GET",), - "ROLE_PERMISSION": "slice_templates:id:content:" - } - } - } - }, - "subscriptions": {"TODO": ("GET", "POST"), - "": {"TODO": ("GET", "DELETE")} - }, + "netslice_templates_content": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "slice_templates:", + "": { + "METHODS": ("GET", "PUT", "DELETE"), + "ROLE_PERMISSION": "slice_templates:id:", + }, + }, + "netslice_templates": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "slice_templates:", + "": { + "METHODS": ("GET", "DELETE"), + "TODO": ("PATCH",), + "ROLE_PERMISSION": "slice_templates:id:", + "nst_content": { + "METHODS": ("GET", "PUT"), + "ROLE_PERMISSION": "slice_templates:id:content:", + }, + "nst": { + "METHODS": ("GET",), # descriptor inside package + "ROLE_PERMISSION": "slice_templates:id:content:", + }, + "artifacts": { + "METHODS": ("GET",), + "ROLE_PERMISSION": "slice_templates:id:content:", + "*": None, + }, + }, + }, + "subscriptions": { + "TODO": ("GET", "POST"), + "": {"TODO": ("GET", "DELETE")}, + }, } }, "nsilcm": { "v1": { - "netslice_instances_content": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "slice_instances:", - "": {"METHODS": ("GET", "DELETE"), - "ROLE_PERMISSION": "slice_instances:id:" - } - }, - "netslice_instances": {"METHODS": ("GET", "POST"), - "ROLE_PERMISSION": "slice_instances:", - "": {"METHODS": ("GET", "DELETE"), - "ROLE_PERMISSION": "slice_instances:id:", - "terminate": {"METHODS": ("POST",), - "ROLE_PERMISSION": "slice_instances:id:terminate:" - }, - "instantiate": {"METHODS": ("POST",), - "ROLE_PERMISSION": "slice_instances:id:instantiate:" - }, - "action": {"METHODS": ("POST",), - "ROLE_PERMISSION": "slice_instances:id:action:" - }, - } - }, - "nsi_lcm_op_occs": {"METHODS": ("GET",), - "ROLE_PERMISSION": "slice_instances:opps:", - "": {"METHODS": ("GET",), - "ROLE_PERMISSION": "slice_instances:opps:id:", - }, - }, + "netslice_instances_content": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "slice_instances:", + "": { + "METHODS": ("GET", "DELETE"), + "ROLE_PERMISSION": "slice_instances:id:", + }, + }, + "netslice_instances": { + "METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "slice_instances:", + "": { + "METHODS": ("GET", "DELETE"), + "ROLE_PERMISSION": "slice_instances:id:", + "terminate": { + "METHODS": ("POST",), + "ROLE_PERMISSION": "slice_instances:id:terminate:", + }, + "instantiate": { + "METHODS": ("POST",), + "ROLE_PERMISSION": "slice_instances:id:instantiate:", + }, + "action": { + "METHODS": ("POST",), + "ROLE_PERMISSION": "slice_instances:id:action:", + }, + }, + }, + "nsi_lcm_op_occs": { + "METHODS": ("GET",), + "ROLE_PERMISSION": "slice_instances:opps:", + "": { + "METHODS": ("GET",), + "ROLE_PERMISSION": "slice_instances:opps:id:", + }, + }, } }, "nspm": { @@ -438,9 +560,10 @@ valid_url_methods = { "pm_jobs": { "": { "reports": { - "": {"METHODS": ("GET",), - "ROLE_PERMISSION": "reports:id:", - } + "": { + "METHODS": ("GET",), + "ROLE_PERMISSION": "reports:id:", + } } }, }, @@ -450,7 +573,6 @@ valid_url_methods = { class NbiException(Exception): - def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED): Exception.__init__(self, message) self.http_code = http_code @@ -463,8 +585,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: @@ -479,31 +601,45 @@ class Server(object): cherrypy.request.headers.pop("Content-File-MD5", None) elif "application/yaml" in cherrypy.request.headers["Content-Type"]: error_text = "Invalid yaml format " - indata = yaml.load(cherrypy.request.body) + indata = yaml.load( + cherrypy.request.body, Loader=yaml.SafeLoader + ) cherrypy.request.headers.pop("Content-File-MD5", None) - elif "application/binary" in cherrypy.request.headers["Content-Type"] or \ - "application/gzip" in cherrypy.request.headers["Content-Type"] or \ - "application/zip" in cherrypy.request.headers["Content-Type"] or \ - "text/plain" in cherrypy.request.headers["Content-Type"]: + elif ( + "application/binary" in cherrypy.request.headers["Content-Type"] + or "application/gzip" + in cherrypy.request.headers["Content-Type"] + or "application/zip" in cherrypy.request.headers["Content-Type"] + or "text/plain" in cherrypy.request.headers["Content-Type"] + ): indata = cherrypy.request.body # .read() - elif "multipart/form-data" in cherrypy.request.headers["Content-Type"]: + elif ( + "multipart/form-data" + in cherrypy.request.headers["Content-Type"] + ): if "descriptor_file" in kwargs: filecontent = kwargs.pop("descriptor_file") if not filecontent.file: - raise NbiException("empty file or content", HTTPStatus.BAD_REQUEST) + raise NbiException( + "empty file or content", HTTPStatus.BAD_REQUEST + ) indata = filecontent.file # .read() if filecontent.content_type.value: - cherrypy.request.headers["Content-Type"] = filecontent.content_type.value + cherrypy.request.headers[ + "Content-Type" + ] = filecontent.content_type.value else: # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable, # "Only 'Content-Type' of type 'application/json' or # 'application/yaml' for input format are available") error_text = "Invalid yaml format " - indata = yaml.load(cherrypy.request.body) + indata = yaml.load( + cherrypy.request.body, Loader=yaml.SafeLoader + ) cherrypy.request.headers.pop("Content-File-MD5", None) else: error_text = "Invalid yaml format " - indata = yaml.load(cherrypy.request.body) + indata = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader) cherrypy.request.headers.pop("Content-File-MD5", None) if not indata: indata = {} @@ -518,10 +654,15 @@ class Server(object): kwargs[k] = None elif format_yaml: try: - kwargs[k] = yaml.load(v) + kwargs[k] = yaml.load(v, Loader=yaml.SafeLoader) except Exception: pass - elif k.endswith(".gt") or k.endswith(".lt") or k.endswith(".gte") or k.endswith(".lte"): + elif ( + k.endswith(".gt") + or k.endswith(".lt") + or k.endswith(".gte") + or k.endswith(".lte") + ): try: kwargs[k] = int(v) except Exception: @@ -537,7 +678,7 @@ class Server(object): v[index] = None elif format_yaml: try: - v[index] = yaml.load(v[index]) + v[index] = yaml.load(v[index], Loader=yaml.SafeLoader) except Exception: pass @@ -545,7 +686,9 @@ class Server(object): except (ValueError, yaml.YAMLError) as exc: raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST) except KeyError as exc: - raise NbiException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST) + raise NbiException( + "Query string error: " + str(exc), HTTPStatus.BAD_REQUEST + ) except Exception as exc: raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST) @@ -555,42 +698,51 @@ class Server(object): return string of dictionary data according to requested json, yaml, xml. By default json :param data: response to be sent. Can be a dict, text or file :param token_info: Contains among other username and project - :param _format: The format to be set as Content-Type ir data is a file + :param _format: The format to be set as Content-Type if data is a file :return: None """ accept = cherrypy.request.headers.get("Accept") if data is None: if accept and "text/html" in accept: - return html.format(data, cherrypy.request, cherrypy.response, token_info) + return html.format( + data, cherrypy.request, cherrypy.response, token_info + ) # cherrypy.response.status = HTTPStatus.NO_CONTENT.value return elif hasattr(data, "read"): # file object if _format: cherrypy.response.headers["Content-Type"] = _format elif "b" in data.mode: # binariy asssumig zip - cherrypy.response.headers["Content-Type"] = 'application/zip' + cherrypy.response.headers["Content-Type"] = "application/zip" else: - cherrypy.response.headers["Content-Type"] = 'text/plain' + cherrypy.response.headers["Content-Type"] = "text/plain" # 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' + if "text/html" in accept: + return html.format( + data, cherrypy.request, cherrypy.response, token_info + ) + elif "application/yaml" in accept or "*/*" in accept: + pass + 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") - elif "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") - 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='"' + 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='"' @cherrypy.expose def index(self, *args, **kwargs): @@ -598,10 +750,12 @@ class Server(object): try: if cherrypy.request.method == "GET": token_info = self.authenticator.authorize() - outdata = token_info # Home page + outdata = token_info # Home page else: - raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value, - "Method {} not allowed for tokens".format(cherrypy.request.method)) + raise cherrypy.HTTPError( + HTTPStatus.METHOD_NOT_ALLOWED.value, + "Method {} not allowed for tokens".format(cherrypy.request.method), + ) return self._format_out(outdata, token_info) @@ -613,13 +767,39 @@ class Server(object): @cherrypy.expose def version(self, *args, **kwargs): # TODO consider to remove and provide version using the static version file - global __version__, version_date try: if cherrypy.request.method != "GET": - raise NbiException("Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED) + raise NbiException( + "Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED + ) elif args or kwargs: - raise NbiException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED) - return __version__ + " " + version_date + raise NbiException( + "Invalid URL or query string for version", + HTTPStatus.METHOD_NOT_ALLOWED, + ) + # TODO include version of other modules, pick up from some kafka admin message + osm_nbi_version = {"version": nbi_version, "date": nbi_version_date} + return self._format_out(osm_nbi_version) + 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) + + 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 = { @@ -649,7 +829,10 @@ class Server(object): # self.engine.load_dbase(cherrypy.request.app.config) indata = self._format_in(kwargs) if not isinstance(indata, dict): - raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST) + raise NbiException( + "Expected application/yaml or application/json Content-Type", + HTTPStatus.BAD_REQUEST, + ) if method == "GET": token_info = self.authenticator.authorize() @@ -668,8 +851,10 @@ class Server(object): indata.update(kwargs) # This is needed to log the user when authentication fails cherrypy.request.login = "{}".format(indata.get("username", "-")) - outdata = token_info = self.authenticator.new_token(token_info, indata, cherrypy.request.remote) - cherrypy.session['Authorization'] = outdata["_id"] + outdata = token_info = self.authenticator.new_token( + token_info, indata, cherrypy.request.remote + ) + cherrypy.session["Authorization"] = outdata["_id"] self._set_location_header("admin", "v1", "tokens", outdata["_id"]) # for logging self._format_login(token_info) @@ -686,19 +871,30 @@ class Server(object): token_id = token_info["_id"] outdata = self.authenticator.del_token(token_id) token_info = None - cherrypy.session['Authorization'] = "logout" + cherrypy.session["Authorization"] = "logout" # cherrypy.response.cookie["Authorization"] = token_id # cherrypy.response.cookie["Authorization"]['expires'] = 0 else: - raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED) + raise NbiException( + "Method {} not allowed for token".format(method), + HTTPStatus.METHOD_NOT_ALLOWED, + ) return self._format_out(outdata, token_info) @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"\
-                   "sleep/
" + return ( + "
\ninit\nfile/  download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
+                "sleep/
" + ) elif args and args[0] == "init": try: @@ -709,10 +905,15 @@ class Server(object): cherrypy.response.status = HTTPStatus.FORBIDDEN.value return self._format_out("Database already initialized") elif args and args[0] == "file": - return cherrypy.lib.static.serve_file(cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1], - "text/plain", "attachment") + return cherrypy.lib.static.serve_file( + cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1], + "text/plain", + "attachment", + ) elif args and args[0] == "file2": - f_path = cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1] + f_path = ( + cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1] + ) f = open(f_path, "r") cherrypy.response.headers["Content-type"] = "text/plain" return f @@ -730,11 +931,15 @@ class Server(object): return ",".join(folders) + " folders deleted\n" elif args and args[0] == "login": if not cherrypy.request.headers.get("Authorization"): - cherrypy.response.headers["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"' + cherrypy.response.headers[ + "WWW-Authenticate" + ] = 'Basic realm="Access to OSM site", charset="UTF-8"' cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value elif args and args[0] == "login2": if not cherrypy.request.headers.get("Authorization"): - cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"' + cherrypy.response.headers[ + "WWW-Authenticate" + ] = 'Bearer realm="Access to OSM site"' cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value elif args and args[0] == "sleep": sleep_time = 5 @@ -751,35 +956,42 @@ class Server(object): main_topic = args[1] return_text = "
{} ->\n".format(main_topic)
             try:
-                if cherrypy.request.method == 'POST':
-                    to_send = yaml.load(cherrypy.request.body)
+                if cherrypy.request.method == "POST":
+                    to_send = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
                     for k, v in to_send.items():
                         self.engine.msg.write(main_topic, k, v)
                         return_text += "  {}: {}\n".format(k, v)
-                elif cherrypy.request.method == 'GET':
+                elif cherrypy.request.method == "GET":
                     for k, v in kwargs.items():
-                        self.engine.msg.write(main_topic, k, yaml.load(v))
-                        return_text += "  {}: {}\n".format(k, yaml.load(v))
+                        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" return return_text return_text = ( - "
\nheaders:\n  args: {}\n".format(args) +
-            "  kwargs: {}\n".format(kwargs) +
-            "  headers: {}\n".format(cherrypy.request.headers) +
-            "  path_info: {}\n".format(cherrypy.request.path_info) +
-            "  query_string: {}\n".format(cherrypy.request.query_string) +
-            "  session: {}\n".format(cherrypy.session) +
-            "  cookie: {}\n".format(cherrypy.request.cookie) +
-            "  method: {}\n".format(cherrypy.request.method) +
-            "  session: {}\n".format(cherrypy.session.get('fieldname')) +
-            "  body:\n")
+            "
\nheaders:\n  args: {}\n".format(args)
+            + "  kwargs: {}\n".format(kwargs)
+            + "  headers: {}\n".format(cherrypy.request.headers)
+            + "  path_info: {}\n".format(cherrypy.request.path_info)
+            + "  query_string: {}\n".format(cherrypy.request.query_string)
+            + "  session: {}\n".format(cherrypy.session)
+            + "  cookie: {}\n".format(cherrypy.request.cookie)
+            + "  method: {}\n".format(cherrypy.request.method)
+            + "  session: {}\n".format(cherrypy.session.get("fieldname"))
+            + "  body:\n"
+        )
         return_text += "    length: {}\n".format(cherrypy.request.body.length)
         if cherrypy.request.body.length:
             return_text += "    content: {}\n".format(
-                str(cherrypy.request.body.read(int(cherrypy.request.headers.get('Content-Length', 0)))))
+                str(
+                    cherrypy.request.body.read(
+                        int(cherrypy.request.headers.get("Content-Length", 0))
+                    )
+                )
+            )
         if thread_info:
             return_text += "thread: {}\n".format(thread_info)
         return_text += "
" @@ -788,29 +1000,44 @@ class Server(object): @staticmethod def _check_valid_url_method(method, *args): if len(args) < 3: - raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus.METHOD_NOT_ALLOWED) + raise NbiException( + "URL must contain at least 'main_topic/version/topic'", + HTTPStatus.METHOD_NOT_ALLOWED, + ) reference = valid_url_methods for arg in args: if arg is None: break if not isinstance(reference, dict): - raise NbiException("URL contains unexpected extra items '{}'".format(arg), - HTTPStatus.METHOD_NOT_ALLOWED) + raise NbiException( + "URL contains unexpected extra items '{}'".format(arg), + HTTPStatus.METHOD_NOT_ALLOWED, + ) if arg in reference: reference = reference[arg] 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) + raise NbiException( + "Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED + ) if "TODO" in reference and method in reference["TODO"]: - raise NbiException("Method {} not supported yet for this URL".format(method), HTTPStatus.NOT_IMPLEMENTED) + raise NbiException( + "Method {} not supported yet for this URL".format(method), + HTTPStatus.NOT_IMPLEMENTED, + ) elif "METHODS" in reference and method not in reference["METHODS"]: - raise NbiException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED) + raise NbiException( + "Method {} not supported for this URL".format(method), + HTTPStatus.METHOD_NOT_ALLOWED, + ) return reference["ROLE_PERMISSION"] + method.lower() @staticmethod @@ -824,7 +1051,9 @@ class Server(object): :return: None """ # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT - cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(main_topic, version, topic, id) + cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format( + main_topic, version, topic, id + ) return @staticmethod @@ -857,17 +1086,27 @@ class Server(object): set_project: tuple with projects that a created element will belong to method: show, list, delete, write """ - admin_query = {"force": False, "project_id": (token_info["project_id"], ), "username": token_info["username"], - "admin": token_info["admin"], "public": None} + admin_query = { + "force": False, + "project_id": (token_info["project_id"],), + "username": token_info["username"], + "admin": token_info["admin"], + "public": None, + "allow_show_user_project_role": token_info["allow_show_user_project_role"], + } if kwargs: # FORCE if "FORCE" in kwargs: - if kwargs["FORCE"].lower() != "false": # if None or True set force to True + if ( + kwargs["FORCE"].lower() != "false" + ): # if None or True set force to True admin_query["force"] = True del kwargs["FORCE"] # PUBLIC if "PUBLIC" in kwargs: - if kwargs["PUBLIC"].lower() != "false": # if None or True set public to True + if ( + kwargs["PUBLIC"].lower() != "false" + ): # if None or True set public to True admin_query["public"] = True else: admin_query["public"] = False @@ -877,25 +1116,33 @@ class Server(object): behave_as = kwargs.pop("ADMIN") if behave_as.lower() != "false": if not token_info["admin"]: - raise NbiException("Only admin projects can use 'ADMIN' query string", HTTPStatus.UNAUTHORIZED) - if not behave_as or behave_as.lower() == "true": # convert True, None to empty list + raise NbiException( + "Only admin projects can use 'ADMIN' query string", + HTTPStatus.UNAUTHORIZED, + ) + if ( + not behave_as or behave_as.lower() == "true" + ): # convert True, None to empty list admin_query["project_id"] = () elif isinstance(behave_as, (list, tuple)): admin_query["project_id"] = behave_as - else: # isinstance(behave_as, str) - admin_query["project_id"] = (behave_as, ) + else: # isinstance(behave_as, str) + admin_query["project_id"] = (behave_as,) if "SET_PROJECT" in kwargs: set_project = kwargs.pop("SET_PROJECT") if not set_project: admin_query["set_project"] = list(admin_query["project_id"]) else: if isinstance(set_project, str): - set_project = (set_project, ) + set_project = (set_project,) if admin_query["project_id"]: for p in set_project: if p not in admin_query["project_id"]: - raise NbiException("Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or " - "'ADMIN='{p}'".format(p=p), HTTPStatus.UNAUTHORIZED) + raise NbiException( + "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or " + "'ADMIN='{p}'".format(p=p), + HTTPStatus.UNAUTHORIZED, + ) admin_query["set_project"] = set_project # PROJECT_READ @@ -914,7 +1161,16 @@ class Server(object): return admin_query @cherrypy.expose - def default(self, main_topic=None, version=None, topic=None, _id=None, item=None, *args, **kwargs): + def default( + self, + main_topic=None, + version=None, + topic=None, + _id=None, + item=None, + *args, + **kwargs + ): token_info = None outdata = None _format = None @@ -924,30 +1180,56 @@ class Server(object): engine_session = None try: if not main_topic or not version or not topic: - raise NbiException("URL must contain at least 'main_topic/version/topic'", - HTTPStatus.METHOD_NOT_ALLOWED) - if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm", "nspm"): - raise NbiException("URL main_topic '{}' not supported".format(main_topic), - HTTPStatus.METHOD_NOT_ALLOWED) - if version != 'v1': - raise NbiException("URL version '{}' not supported".format(version), HTTPStatus.METHOD_NOT_ALLOWED) - - if kwargs and "METHOD" in kwargs and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"): + raise NbiException( + "URL must contain at least 'main_topic/version/topic'", + HTTPStatus.METHOD_NOT_ALLOWED, + ) + if main_topic not in ( + "admin", + "vnfpkgm", + "nsd", + "nslcm", + "pdu", + "nst", + "nsilcm", + "nspm", + ): + raise NbiException( + "URL main_topic '{}' not supported".format(main_topic), + HTTPStatus.METHOD_NOT_ALLOWED, + ) + if version != "v1": + raise NbiException( + "URL version '{}' not supported".format(version), + HTTPStatus.METHOD_NOT_ALLOWED, + ) + + if ( + kwargs + and "METHOD" in kwargs + and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH") + ): method = kwargs.pop("METHOD") else: method = cherrypy.request.method - role_permission = self._check_valid_url_method(method, main_topic, version, topic, _id, item, *args) - query_string_operations = self._extract_query_string_operations(kwargs, method) + role_permission = self._check_valid_url_method( + method, main_topic, version, topic, _id, item, *args + ) + query_string_operations = self._extract_query_string_operations( + kwargs, method + ) if main_topic == "admin" and topic == "tokens": return self.token(method, _id, kwargs) - - token_info = self.authenticator.authorize(role_permission, query_string_operations) + 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 @@ -955,6 +1237,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": @@ -969,11 +1255,24 @@ class Server(object): engine_topic = "nsilcmops" elif main_topic == "pdu": engine_topic = "pdus" - if engine_topic == "vims": # TODO this is for backward compatibility, it will be removed in the future + 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 ( + "nsd_content", + "package_content", + "artifacts", + "vnfd", + "nsd", + "nst", + "nst_content", + ): if item in ("vnfd", "nsd", "nst"): path = "$DESCRIPTOR" elif args: @@ -982,25 +1281,52 @@ class Server(object): path = () else: path = None - file, _format = self.engine.get_file(engine_session, engine_topic, _id, path, - cherrypy.request.headers.get("Accept")) + file, _format = self.engine.get_file( + engine_session, + engine_topic, + _id, + path, + cherrypy.request.headers.get("Accept"), + ) outdata = file elif not _id: - outdata = self.engine.get_item_list(engine_session, engine_topic, kwargs) + outdata = self.engine.get_item_list( + engine_session, engine_topic, kwargs, api_req=True + ) else: if item == "reports": # TODO check that project_id (_id in this context) has permissions _id = args[0] - outdata = self.engine.get_item(engine_session, engine_topic, _id) + filter_q = None + if "vcaStatusRefresh" in kwargs: + filter_q = {"vcaStatusRefresh": kwargs["vcaStatusRefresh"]} + outdata = self.engine.get_item(engine_session, engine_topic, _id, filter_q, True) + elif method == "POST": cherrypy.response.status = HTTPStatus.CREATED.value - if topic in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"): + if topic in ( + "ns_descriptors_content", + "vnf_packages_content", + "netslice_templates_content", + ): _id = cherrypy.request.headers.get("Transaction-Id") if not _id: - _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, {}, None, - cherrypy.request.headers) - completed = self.engine.upload_content(engine_session, engine_topic, _id, indata, kwargs, - cherrypy.request.headers) + _id, _ = self.engine.new_item( + rollback, + engine_session, + engine_topic, + {}, + None, + cherrypy.request.headers, + ) + completed = self.engine.upload_content( + engine_session, + engine_topic, + _id, + indata, + kwargs, + cherrypy.request.headers, + ) if completed: self._set_location_header(main_topic, version, topic, _id) else: @@ -1008,39 +1334,85 @@ class Server(object): outdata = {"id": _id} elif topic == "ns_instances_content": # creates NSR - _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs) + _id, _ = self.engine.new_item( + rollback, engine_session, engine_topic, indata, kwargs + ) # creates nslcmop indata["lcmOperationType"] = "instantiate" indata["nsInstanceId"] = _id - nslcmop_id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", indata, None) + nslcmop_id, _ = self.engine.new_item( + rollback, engine_session, "nslcmops", indata, None + ) self._set_location_header(main_topic, version, topic, _id) outdata = {"id": _id, "nslcmop_id": nslcmop_id} elif topic == "ns_instances" and item: indata["lcmOperationType"] = item indata["nsInstanceId"] = _id - _id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", indata, kwargs) - self._set_location_header(main_topic, version, "ns_lcm_op_occs", _id) + _id, _ = self.engine.new_item( + rollback, engine_session, "nslcmops", indata, kwargs + ) + self._set_location_header( + main_topic, version, "ns_lcm_op_occs", _id + ) outdata = {"id": _id} cherrypy.response.status = HTTPStatus.ACCEPTED.value elif topic == "netslice_instances_content": # creates NetSlice_Instance_record (NSIR) - _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs) + _id, _ = self.engine.new_item( + rollback, engine_session, engine_topic, indata, kwargs + ) self._set_location_header(main_topic, version, topic, _id) indata["lcmOperationType"] = "instantiate" indata["netsliceInstanceId"] = _id - nsilcmop_id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", indata, kwargs) + 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 - _id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", indata, kwargs) - self._set_location_header(main_topic, version, "nsi_lcm_op_occs", _id) + _id, _ = self.engine.new_item( + rollback, engine_session, "nsilcmops", indata, kwargs + ) + 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) + _id, op_id = self.engine.new_item( + rollback, + engine_session, + engine_topic, + indata, + kwargs, + cherrypy.request.headers, + ) self._set_location_header(main_topic, version, topic, _id) outdata = {"id": _id} if op_id: @@ -1050,50 +1422,74 @@ class Server(object): elif method == "DELETE": if not _id: - outdata = self.engine.del_item_list(engine_session, engine_topic, kwargs) + 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 + "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 - elif topic == "netslice_instances_content" and not engine_session["force"]: + 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 + "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 if not indata and not kwargs and not engine_session.get("set_project"): - raise NbiException("Nothing to update. Provide payload and/or query string", - HTTPStatus.BAD_REQUEST) - if item in ("nsd_content", "package_content", "nst_content") and method == "PUT": - completed = self.engine.upload_content(engine_session, engine_topic, _id, indata, kwargs, - cherrypy.request.headers) + raise NbiException( + "Nothing to update. Provide payload and/or query string", + HTTPStatus.BAD_REQUEST, + ) + if ( + item in ("nsd_content", "package_content", "nst_content") + and method == "PUT" + ): + completed = self.engine.upload_content( + engine_session, + engine_topic, + _id, + indata, + kwargs, + cherrypy.request.headers, + ) if not completed: cherrypy.response.headers["Transaction-Id"] = id else: - op_id = self.engine.edit_item(engine_session, engine_topic, _id, indata, kwargs) + op_id = self.engine.edit_item( + engine_session, engine_topic, _id, indata, kwargs + ) if op_id: cherrypy.response.status = HTTPStatus.ACCEPTED.value @@ -1102,20 +1498,45 @@ class Server(object): cherrypy.response.status = HTTPStatus.NO_CONTENT.value outdata = None else: - raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED) + raise NbiException( + "Method {} not allowed".format(method), + HTTPStatus.METHOD_NOT_ALLOWED, + ) # 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, - ValidationError, AuthconnException)): + if isinstance( + e, + ( + NbiException, + EngineException, + DbException, + FsException, + MsgException, + AuthException, + ValidationError, + AuthconnException, + ), + ): http_code_value = cherrypy.response.status = e.http_code.value http_code_name = e.http_code.name cherrypy.log("Exception {}".format(e)) else: - http_code_value = cherrypy.response.status = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR + http_code_value = ( + cherrypy.response.status + ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True) http_code_name = HTTPStatus.BAD_REQUEST.name if hasattr(outdata, "close"): # is an open file @@ -1125,13 +1546,28 @@ class Server(object): for rollback_item in rollback: try: 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) + 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) + self.engine.db.del_one( + rollback_item["topic"], + {"_id": rollback_item["_id"]}, + fail_on_empty=False, + ) except Exception as e2: - rollback_error_text = "Rollback Exception {}: {}".format(rollback_item, e2) + rollback_error_text = "Rollback Exception {}: {}".format( + rollback_item, e2 + ) cherrypy.log(rollback_error_text) error_text += ". " + rollback_error_text # if isinstance(e, MsgException): @@ -1150,7 +1586,9 @@ class Server(object): if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict): for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"): if outdata.get(logging_id): - cherrypy.request.login += ";{}={}".format(logging_id, outdata[logging_id][:36]) + cherrypy.request.login += ";{}={}".format( + logging_id, outdata[logging_id][:36] + ) def _start_service(): @@ -1166,7 +1604,7 @@ def _start_service(): # update general cherrypy configuration update_dict = {} - engine_config = cherrypy.tree.apps['/osm'].config + engine_config = cherrypy.tree.apps["/osm"].config for k, v in environ.items(): if not k.startswith("OSMNBI_"): continue @@ -1175,15 +1613,15 @@ def _start_service(): continue try: # update static configuration - if k == 'OSMNBI_STATIC_DIR': - engine_config["/static"]['tools.staticdir.dir'] = v - engine_config["/static"]['tools.staticdir.on'] = True - elif k == 'OSMNBI_SOCKET_PORT' or k == 'OSMNBI_SERVER_PORT': - update_dict['server.socket_port'] = int(v) - elif k == 'OSMNBI_SOCKET_HOST' or k == 'OSMNBI_SERVER_HOST': - update_dict['server.socket_host'] = v + if k == "OSMNBI_STATIC_DIR": + engine_config["/static"]["tools.staticdir.dir"] = v + engine_config["/static"]["tools.staticdir.on"] = True + elif k == "OSMNBI_SOCKET_PORT" or k == "OSMNBI_SERVER_PORT": + update_dict["server.socket_port"] = int(v) + elif k == "OSMNBI_SOCKET_HOST" or k == "OSMNBI_SERVER_HOST": + update_dict["server.socket_host"] = v elif k1 in ("server", "test", "auth", "log"): - update_dict[k1 + '.' + k2] = v + update_dict[k1 + "." + k2] = v elif k1 in ("message", "database", "storage", "authentication"): # k2 = k2.replace('_', '.') if k2 in ("port", "db_port"): @@ -1201,26 +1639,34 @@ def _start_service(): engine_config["global"].update(update_dict) # logging cherrypy - log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s" - log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S') + log_format_simple = ( + "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s" + ) + log_formatter_simple = logging.Formatter( + log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S" + ) logger_server = logging.getLogger("cherrypy.error") logger_access = logging.getLogger("cherrypy.access") logger_cherry = logging.getLogger("cherrypy") logger_nbi = logging.getLogger("nbi") if "log.file" in engine_config["global"]: - file_handler = logging.handlers.RotatingFileHandler(engine_config["global"]["log.file"], - maxBytes=100e6, backupCount=9, delay=0) + file_handler = logging.handlers.RotatingFileHandler( + engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0 + ) file_handler.setFormatter(log_formatter_simple) logger_cherry.addHandler(file_handler) logger_nbi.addHandler(file_handler) # log always to standard output - for format_, logger in {"nbi.server %(filename)s:%(lineno)s": logger_server, - "nbi.access %(filename)s:%(lineno)s": logger_access, - "%(name)s %(filename)s:%(lineno)s": logger_nbi - }.items(): + for format_, logger in { + "nbi.server %(filename)s:%(lineno)s": logger_server, + "nbi.access %(filename)s:%(lineno)s": logger_access, + "%(name)s %(filename)s:%(lineno)s": logger_nbi, + }.items(): log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_) - log_formatter_cherry = logging.Formatter(log_format_cherry, datefmt='%Y-%m-%dT%H:%M:%S') + log_formatter_cherry = logging.Formatter( + log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S" + ) str_handler = logging.StreamHandler() str_handler.setFormatter(log_formatter_cherry) logger.addHandler(str_handler) @@ -1230,37 +1676,42 @@ def _start_service(): logger_nbi.setLevel(engine_config["global"]["log.level"]) # logging other modules - for k1, logname in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items(): + for k1, logname in { + "message": "nbi.msg", + "database": "nbi.db", + "storage": "nbi.fs", + }.items(): engine_config[k1]["logger_name"] = logname logger_module = logging.getLogger(logname) if "logfile" in engine_config[k1]: - file_handler = logging.handlers.RotatingFileHandler(engine_config[k1]["logfile"], - maxBytes=100e6, backupCount=9, delay=0) + file_handler = logging.handlers.RotatingFileHandler( + engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0 + ) file_handler.setFormatter(log_formatter_simple) logger_module.addHandler(file_handler) if "loglevel" in engine_config[k1]: logger_module.setLevel(engine_config[k1]["loglevel"]) # TODO add more entries, e.g.: storage - cherrypy.tree.apps['/osm'].root.engine.start(engine_config) - cherrypy.tree.apps['/osm'].root.authenticator.start(engine_config) - cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version) - cherrypy.tree.apps['/osm'].root.authenticator.init_db(target_version=auth_database_version) + cherrypy.tree.apps["/osm"].root.engine.start(engine_config) + cherrypy.tree.apps["/osm"].root.authenticator.start(engine_config) + cherrypy.tree.apps["/osm"].root.engine.init_db(target_version=database_version) + cherrypy.tree.apps["/osm"].root.authenticator.init_db( + target_version=auth_database_version + ) # start subscriptions thread: - subscription_thread = SubscriptionThread(config=engine_config, engine=nbi_server.engine) + subscription_thread = SubscriptionThread( + config=engine_config, engine=nbi_server.engine + ) subscription_thread.start() # Do not capture except SubscriptionException - # load and print version. Ignore possible errors, e.g. file not found - try: - with open("{}/version".format(engine_config["/static"]['tools.staticdir.dir'])) as version_file: - version_data = version_file.read() - version = version_data.replace("\n", " ") - backend = engine_config["authentication"]["backend"] - cherrypy.log.error("Starting OSM NBI Version {} with {} authentication backend" - .format(version, 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(): @@ -1272,7 +1723,7 @@ def _stop_service(): if subscription_thread: subscription_thread.terminate() subscription_thread = None - cherrypy.tree.apps['/osm'].root.engine.stop() + cherrypy.tree.apps["/osm"].root.engine.stop() cherrypy.log.error("Stopping osm_nbi") @@ -1296,21 +1747,25 @@ def nbi(config_file): # 'tools.auth_basic.realm': 'localhost', # 'tools.auth_basic.checkpassword': validate_password}) nbi_server = Server() - cherrypy.engine.subscribe('start', _start_service) - cherrypy.engine.subscribe('stop', _stop_service) - cherrypy.quickstart(nbi_server, '/osm', config_file) + cherrypy.engine.subscribe("start", _start_service) + cherrypy.engine.subscribe("stop", _stop_service) + cherrypy.quickstart(nbi_server, "/osm", config_file) def usage(): - print("""Usage: {} [options] + print( + """Usage: {} [options] -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg) -h|--help: shows this help - """.format(sys.argv[0])) + """.format( + sys.argv[0] + ) + ) # --log-socket-host HOST: send logs to this host") # --log-socket-port PORT: send logs using this port (default: 9022)") -if __name__ == '__main__': +if __name__ == "__main__": try: # load parameters and configuration opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"]) @@ -1332,14 +1787,24 @@ if __name__ == '__main__': assert False, "Unhandled option" if config_file: if not path.isfile(config_file): - print("configuration file '{}' that not exist".format(config_file), file=sys.stderr) + print( + "configuration file '{}' that not exist".format(config_file), + file=sys.stderr, + ) exit(1) else: - for config_file in (__file__[:__file__.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"): + for config_file in ( + __file__[: __file__.rfind(".")] + ".cfg", + "./nbi.cfg", + "/etc/osm/nbi.cfg", + ): if path.isfile(config_file): break else: - print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys.stderr) + print( + "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", + file=sys.stderr, + ) exit(1) nbi(config_file) except getopt.GetoptError as e: