from authconn import AuthException
from auth import Authenticator
from engine import Engine, EngineException
+from validation import ValidationError
from osm_common.dbbase import DbException
from osm_common.fsbase import FsException
from osm_common.msgbase import MsgException
"""
North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
URL: /osm GET POST PUT DELETE PATCH
- /nsd/v1 O O
+ /nsd/v1
/ns_descriptors_content O O
/<nsdInfoId> O O O O
/ns_descriptors O5 O5
/<vnfInstanceId> O
/subscriptions 5 5
/<subscriptionId> 5 X
+
/pdu/v1
/pdu_descriptor O O
/<id> O O O O
+
/admin/v1
/tokens O O
/<id> O O
/sdns O O
/<id> O O O
+ /nst/v1 O O
+ /netslice_templates_content O O
+ /<nstInfoId> O O O O
+ /netslice_templates O O
+ /<nstInfoId> O O O
+ /nst_content O O
+ /nst O
+ /artifacts[/<artifactPath>] O
+ /subscriptions X X
+ /<subscriptionId> X X
+
+ /nsilcm/v1
+ /netslice_instances_content O O
+ /<SliceInstanceId> O O
+ /netslice_instances O O
+ /<SliceInstanceId> O O
+ instantiate O
+ terminate O
+ action O
+ /nsi_lcm_op_occs O O
+ /<nsiLcmOpOccId> O O O
+ /subscriptions X X
+ /<subscriptionId> X X
+
query string:
Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
+ simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
+ filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
+ op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
+ attrName := string
For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
item of the array, that is, pass if any item of the array pass the filter.
It allows both ne and neq for not equal
"<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
},
"ns_descriptors": {"METHODS": ("GET", "POST"),
- "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH",
+ "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
"nsd_content": {"METHODS": ("GET", "PUT")},
"nsd": {"METHODS": "GET"}, # descriptor inside package
"artifacts": {"*": {"METHODS": "GET"}}
},
}
},
+ "nst": {
+ "v1": {
+ "netslice_templates_content": {"METHODS": ("GET", "POST"),
+ "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
+ },
+ "netslice_templates": {"METHODS": ("GET", "POST"),
+ "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH",
+ "nst_content": {"METHODS": ("GET", "PUT")},
+ "nst": {"METHODS": "GET"}, # descriptor inside package
+ "artifacts": {"*": {"METHODS": "GET"}}
+ }
+ },
+ "subscriptions": {"TODO": ("GET", "POST"),
+ "<ID>": {"TODO": ("GET", "DELETE")}
+ },
+ }
+ },
+ "nsilcm": {
+ "v1": {
+ "netslice_instances_content": {"METHODS": ("GET", "POST"),
+ "<ID>": {"METHODS": ("GET", "DELETE")}
+ },
+ "netslice_instances": {"METHODS": ("GET", "POST"),
+ "<ID>": {"METHODS": ("GET", "DELETE"),
+ "terminate": {"METHODS": "POST"},
+ "instantiate": {"METHODS": "POST"},
+ "action": {"METHODS": "POST"},
+ }
+ },
+ "nsi_lcm_op_occs": {"METHODS": "GET",
+ "<ID>": {"METHODS": "GET"},
+ },
+ }
+ },
}
def _format_in(self, kwargs):
if "application/json" in cherrypy.request.headers["Content-Type"]:
error_text = "Invalid json format "
indata = json.load(self.reader(cherrypy.request.body))
+ 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)
+ 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 \
# 'application/yaml' for input format are available")
error_text = "Invalid yaml format "
indata = yaml.load(cherrypy.request.body)
+ cherrypy.request.headers.pop("Content-File-MD5", None)
else:
error_text = "Invalid yaml format "
indata = yaml.load(cherrypy.request.body)
+ cherrypy.request.headers.pop("Content-File-MD5", None)
if not indata:
indata = {}
return f
elif len(args) == 2 and args[0] == "db-clear":
- return self.engine.del_item_list({"project_id": "admin", "admin": True}, args[1], kwargs)
+ return self.engine.db.del_list(args[1], kwargs)
elif args and args[0] == "prune":
return self.engine.prune()
elif args and args[0] == "login":
time.sleep(sleep_time)
# thread_info
elif len(args) >= 2 and args[0] == "message":
- topic = args[1]
- return_text = "<html><pre>{} ->\n".format(topic)
+ main_topic = args[1]
+ return_text = "<html><pre>{} ->\n".format(main_topic)
try:
if cherrypy.request.method == 'POST':
to_send = yaml.load(cherrypy.request.body)
for k, v in to_send.items():
- self.engine.msg.write(topic, k, v)
+ self.engine.msg.write(main_topic, k, v)
return_text += " {}: {}\n".format(k, v)
elif cherrypy.request.method == 'GET':
for k, v in kwargs.items():
- self.engine.msg.write(topic, k, yaml.load(v))
+ self.engine.msg.write(main_topic, k, yaml.load(v))
return_text += " {}: {}\n".format(k, yaml.load(v))
except Exception as e:
return_text += "Error: " + str(e)
def _check_valid_url_method(self, method, *args):
if len(args) < 3:
- raise NbiException("URL must contain at least 'topic/version/item'", HTTPStatus.METHOD_NOT_ALLOWED)
+ raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus.METHOD_NOT_ALLOWED)
reference = self.valid_methods
for arg in args:
return
@staticmethod
- def _set_location_header(topic, version, item, id):
+ def _set_location_header(main_topic, version, topic, id):
"""
Insert response header Location with the URL of created item base on URL params
- :param topic:
+ :param main_topic:
:param version:
- :param item:
+ :param topic:
:param id:
: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(topic, version, item, id)
+ cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(main_topic, version, topic, id)
return
@cherrypy.expose
- def default(self, topic=None, version=None, item=None, _id=None, item2=None, *args, **kwargs):
+ def default(self, main_topic=None, version=None, topic=None, _id=None, item=None, *args, **kwargs):
session = None
outdata = None
_format = None
method = "DONE"
- engine_item = None
+ engine_topic = None
rollback = []
session = None
try:
- if not topic or not version or not item:
- raise NbiException("URL must contain at least 'topic/version/item'", HTTPStatus.METHOD_NOT_ALLOWED)
- if topic not in ("admin", "vnfpkgm", "nsd", "nslcm"):
- raise NbiException("URL topic '{}' not supported".format(topic), HTTPStatus.METHOD_NOT_ALLOWED)
+ 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"):
+ 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)
force = kwargs.pop("FORCE")
else:
force = False
-
- self._check_valid_url_method(method, topic, version, item, _id, item2, *args)
-
- if topic == "admin" and item == "tokens":
+ self._check_valid_url_method(method, main_topic, version, topic, _id, item, *args)
+ if main_topic == "admin" and topic == "tokens":
return self.token(method, _id, kwargs)
# self.engine.load_dbase(cherrypy.request.app.config)
session = self.authenticator.authorize()
indata = self._format_in(kwargs)
- engine_item = item
- if item == "subscriptions":
- engine_item = topic + "_" + item
- if item2:
- engine_item = item2
-
- if topic == "nsd":
- engine_item = "nsds"
- elif topic == "vnfpkgm":
- engine_item = "vnfds"
- elif topic == "nslcm":
- engine_item = "nsrs"
- if item == "ns_lcm_op_occs":
- engine_item = "nslcmops"
- if item == "vnfrs" or item == "vnf_instances":
- engine_item = "vnfrs"
- elif topic == "pdu":
- engine_item = "pdus"
- if engine_item == "vims": # TODO this is for backward compatibility, it will remove in the future
- engine_item = "vim_accounts"
+ engine_topic = topic
+ if topic == "subscriptions":
+ engine_topic = main_topic + "_" + topic
+ if item:
+ engine_topic = item
+
+ if main_topic == "nsd":
+ engine_topic = "nsds"
+ elif main_topic == "vnfpkgm":
+ engine_topic = "vnfds"
+ elif main_topic == "nslcm":
+ engine_topic = "nsrs"
+ if topic == "ns_lcm_op_occs":
+ engine_topic = "nslcmops"
+ if topic == "vnfrs" or topic == "vnf_instances":
+ engine_topic = "vnfrs"
+ elif main_topic == "nst":
+ engine_topic = "nsts"
+ elif main_topic == "nsilcm":
+ engine_topic = "nsis"
+ if topic == "nsi_lcm_op_occs":
+ engine_topic = "nsilcmops"
+ elif main_topic == "pdu":
+ engine_topic = "pdus"
+ if engine_topic == "vims": # TODO this is for backward compatibility, it will remove in the future
+ engine_topic = "vim_accounts"
if method == "GET":
- if item2 in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd"):
- if item2 in ("vnfd", "nsd"):
+ if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
+ if item in ("vnfd", "nsd", "nst"):
path = "$DESCRIPTOR"
elif args:
path = args
- elif item2 == "artifacts":
+ elif item == "artifacts":
path = ()
else:
path = None
- file, _format = self.engine.get_file(session, engine_item, _id, path,
+ file, _format = self.engine.get_file(session, engine_topic, _id, path,
cherrypy.request.headers.get("Accept"))
outdata = file
elif not _id:
- outdata = self.engine.get_item_list(session, engine_item, kwargs)
+ outdata = self.engine.get_item_list(session, engine_topic, kwargs)
else:
- outdata = self.engine.get_item(session, engine_item, _id)
+ outdata = self.engine.get_item(session, engine_topic, _id)
elif method == "POST":
- if item in ("ns_descriptors_content", "vnf_packages_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, session, engine_item, {}, None, cherrypy.request.headers,
+ _id = self.engine.new_item(rollback, session, engine_topic, {}, None, cherrypy.request.headers,
force=force)
- completed = self.engine.upload_content(session, engine_item, _id, indata, kwargs,
- cherrypy.request.headers)
+ completed = self.engine.upload_content(session, engine_topic, _id, indata, kwargs,
+ cherrypy.request.headers, force=force)
if completed:
- self._set_location_header(topic, version, item, _id)
+ self._set_location_header(main_topic, version, topic, _id)
else:
cherrypy.response.headers["Transaction-Id"] = _id
outdata = {"id": _id}
- elif item == "ns_instances_content":
- _id = self.engine.new_item(rollback, session, engine_item, indata, kwargs, force=force)
- self.engine.ns_operation(rollback, session, _id, "instantiate", indata, None)
- self._set_location_header(topic, version, item, _id)
+ elif topic == "ns_instances_content":
+ # creates NSR
+ _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs, force=force)
+ # creates nslcmop
+ indata["lcmOperationType"] = "instantiate"
+ indata["nsInstanceId"] = _id
+ self.engine.new_item(rollback, session, "nslcmops", indata, None)
+ self._set_location_header(main_topic, version, topic, _id)
outdata = {"id": _id}
- elif item == "ns_instances" and item2:
- _id = self.engine.ns_operation(rollback, session, _id, item2, indata, kwargs)
- self._set_location_header(topic, version, "ns_lcm_op_occs", _id)
+ elif topic == "ns_instances" and item:
+ indata["lcmOperationType"] = item
+ indata["nsInstanceId"] = _id
+ _id = self.engine.new_item(rollback, 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, session, engine_topic, indata, kwargs, force=force)
+ self._set_location_header(main_topic, version, topic, _id)
+ indata["lcmOperationType"] = "instantiate"
+ indata["nsiInstanceId"] = _id
+ self.engine.new_item(rollback, session, "nsilcmops", indata, kwargs)
+ outdata = {"id": _id}
+
+ elif topic == "netslice_instances" and item:
+ indata["lcmOperationType"] = item
+ indata["nsiInstanceId"] = _id
+ _id = self.engine.new_item(rollback, 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
else:
- _id = self.engine.new_item(rollback, session, engine_item, indata, kwargs, cherrypy.request.headers,
- force=force)
- self._set_location_header(topic, version, item, _id)
+ _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs,
+ cherrypy.request.headers, force=force)
+ self._set_location_header(main_topic, version, topic, _id)
outdata = {"id": _id}
- # TODO form NsdInfo when item in ("ns_descriptors", "vnf_packages")
+ # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
cherrypy.response.status = HTTPStatus.CREATED.value
elif method == "DELETE":
if not _id:
- outdata = self.engine.del_item_list(session, engine_item, kwargs)
+ outdata = self.engine.del_item_list(session, engine_topic, kwargs)
cherrypy.response.status = HTTPStatus.OK.value
else: # len(args) > 1
- if item == "ns_instances_content" and not force:
- opp_id = self.engine.ns_operation(rollback, session, _id, "terminate", {"autoremove": True},
- None)
+ if topic == "ns_instances_content" and not force:
+ nslcmop_desc = {
+ "lcmOperationType": "terminate",
+ "nsInstanceId": _id,
+ "autoremove": True
+ }
+ opp_id = self.engine.new_item(rollback, session, "nslcmops", nslcmop_desc, None)
+ outdata = {"_id": opp_id}
+ cherrypy.response.status = HTTPStatus.ACCEPTED.value
+ elif topic == "netslice_instances_content" and not force:
+ nsilcmop_desc = {
+ "lcmOperationType": "terminate",
+ "nsiInstanceId": _id,
+ "autoremove": True
+ }
+ opp_id = self.engine.new_item(rollback, session, "nsilcmops", nsilcmop_desc, None)
outdata = {"_id": opp_id}
cherrypy.response.status = HTTPStatus.ACCEPTED.value
else:
- self.engine.del_item(session, engine_item, _id, force)
+ self.engine.del_item(session, engine_topic, _id, force)
cherrypy.response.status = HTTPStatus.NO_CONTENT.value
- if engine_item in ("vim_accounts", "sdns"):
+ if engine_topic in ("vim_accounts", "sdns"):
cherrypy.response.status = HTTPStatus.ACCEPTED.value
elif method in ("PUT", "PATCH"):
if not indata and not kwargs:
raise NbiException("Nothing to update. Provide payload and/or query string",
HTTPStatus.BAD_REQUEST)
- if item2 in ("nsd_content", "package_content") and method == "PUT":
- completed = self.engine.upload_content(session, engine_item, _id, indata, kwargs,
- cherrypy.request.headers)
+ if item in ("nsd_content", "package_content", "nst_content") and method == "PUT":
+ completed = self.engine.upload_content(session, engine_topic, _id, indata, kwargs,
+ cherrypy.request.headers, force=force)
if not completed:
cherrypy.response.headers["Transaction-Id"] = id
else:
- self.engine.edit_item(session, engine_item, _id, indata, kwargs, force=force)
+ self.engine.edit_item(session, engine_topic, _id, indata, kwargs, force=force)
cherrypy.response.status = HTTPStatus.NO_CONTENT.value
else:
raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
return self._format_out(outdata, session, _format)
- except (NbiException, EngineException, DbException, FsException, MsgException, AuthException) as e:
- cherrypy.log("Exception {}".format(e))
- cherrypy.response.status = e.http_code.value
+ except Exception as e:
+ if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException,
+ ValidationError)):
+ 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
+ cherrypy.log("CRITICAL: Exception {}".format(e))
+ http_code_name = HTTPStatus.BAD_REQUEST.name
if hasattr(outdata, "close"): # is an open file
outdata.close()
+ error_text = str(e)
+ rollback.reverse()
for rollback_item in rollback:
try:
- self.engine.del_item(**rollback_item, session=session, force=True)
+ 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)
+ else:
+ self.engine.del_item(**rollback_item, session=session, force=True)
except Exception as e2:
- cherrypy.log("Rollback Exception {}: {}".format(rollback_item, e2))
- error_text = str(e)
- if isinstance(e, MsgException):
- error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
- engine_item[:-1], method, error_text)
+ rollback_error_text = "Rollback Exception {}: {}".format(rollback_item, e2)
+ cherrypy.log(rollback_error_text)
+ error_text += ". " + rollback_error_text
+ # if isinstance(e, MsgException):
+ # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
+ # engine_topic[:-1], method, error_text)
problem_details = {
- "code": e.http_code.name,
- "status": e.http_code.value,
- "detail": str(e),
+ "code": http_code_name,
+ "status": http_code_value,
+ "detail": error_text,
}
return self._format_out(problem_details, session)
# raise cherrypy.HTTPError(e.http_code.value, str(e))
file_handler.setFormatter(log_formatter_simple)
logger_cherry.addHandler(file_handler)
logger_nbi.addHandler(file_handler)
- else:
- for format_, logger in {"nbi.server": logger_server,
- "nbi.access": 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')
- str_handler = logging.StreamHandler()
- str_handler.setFormatter(log_formatter_cherry)
- logger.addHandler(str_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():
+ 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')
+ str_handler = logging.StreamHandler()
+ str_handler.setFormatter(log_formatter_cherry)
+ logger.addHandler(str_handler)
if engine_config["global"].get("log.level"):
logger_cherry.setLevel(engine_config["global"]["log.level"])
# 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)
- try:
- 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)
- except (EngineException, AuthException):
- pass
+ 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)
# getenv('OSMOPENMANO_TENANT', None)