#!/usr/bin/python3
# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
import cherrypy
import time
import json
import yaml
import html_out as html
import logging
+import logging.handlers
+import getopt
+import sys
+
+from authconn import AuthException
+from auth import Authenticator
from engine import Engine, EngineException
+from subscriptions import SubscriptionThread
+from validation import ValidationError
from osm_common.dbbase import DbException
from osm_common.fsbase import FsException
from osm_common.msgbase import MsgException
-from base64 import standard_b64decode
-#from os import getenv
from http import HTTPStatus
-#from http.client import responses as http_responses
from codecs import getreader
-from os import environ
+from os import environ, path
__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
-# TODO consider to remove and provide version using the static version file
__version__ = "0.1.3"
-version_date = "Apr 2018"
+version_date = "Jan 2019"
database_version = '1.0'
+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
- /nsd/v1 O O
- /ns_descriptors_content O O
- /<nsdInfoId> O O O O
+ /nsd/v1
+ /ns_descriptors_content O O
+ /<nsdInfoId> O O O O
/ns_descriptors O5 O5
/<nsdInfoId> O5 O5 5
/nsd_content O5 O5
/vnfpkgm/v1
/vnf_packages_content O O
- /<vnfPkgId> O O
+ /<vnfPkgId> O O
/vnf_packages O5 O5
/<vnfPkgId> O5 O5 5
/package_content O5 O5
/nslcm/v1
/ns_instances_content O O
- /<nsInstanceId> O O
+ /<nsInstanceId> O O
/ns_instances 5 5
- /<nsInstanceId> 5 5
+ /<nsInstanceId> O5 O5
instantiate O5
terminate O5
action O
/ns_lcm_op_occs 5 5
/<nsLcmOpOccId> 5 5 5
TO BE COMPLETED 5 5
- /vnfrs O
- /<vnfrId> O
+ /vnf_instances (also vnfrs for compatibility) O
+ /<vnfInstanceId> O
/subscriptions 5 5
/<subscriptionId> 5 X
+
+ /pdu/v1
+ /pdu_descriptors O O
+ /<id> O O O O
+
/admin/v1
/tokens O O
- /<id> O O
+ /<id> O O
/users O O
- /<id> O O
+ /<id> O O O O
/projects O O
- /<id> O O
- /vims_accounts (also vims for compatibility) O O
- /<id> O O O
+ /<id> O O
+ /vim_accounts (also vims for compatibility) O O
+ /<id> O O O
+ /wim_accounts O O
+ /<id> O O O
/sdns O O
- /<id> O 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.
- <attrName>[.<attrName>...]*[.<op>]=<value>[,<value>...]&...
- op: "eq"(or empty to one or the values) | "neq" (to any of the values) | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
- all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
+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
+ TODO: 4.3.3 Attribute selectors
+ all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
(none) … same as “exclude_default”
all_fields … all attributes.
- fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not conditionally mandatory, and that are not provided in <list>.
- exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that are not conditionally mandatory, and that are provided in <list>.
- exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for the particular resource
- exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the present specification for the particular resource, but that are not part of <list>
+ fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
+ conditionally mandatory, and that are not provided in <list>.
+ exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
+ are not conditionally mandatory, and that are provided in <list>.
+ exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
+ conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
+ the particular resource
+ exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
+ of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
+ present specification for the particular resource, but that are not part of <list>
+ Additionally it admits some administrator values:
+ FORCE: To force operations skipping dependency checkings
+ ADMIN: To act as an administrator or a different project
+ PUBLIC: To get public descriptors or set a descriptor as public
+ SET_PROJECT: To make a descriptor available for other project
+
Header field name Reference Example Descriptions
Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
This header field shall be present if the response is expected to have a non-empty message body.
Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
This header field shall be present if the request has a non-empty message body.
- Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request. Details are specified in clause 4.5.3.
+ Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
+ Details are specified in clause 4.5.3.
Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
Header field name Reference Example Descriptions
Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
This header field shall be present if the response has a non-empty message body.
- Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a new resource has been created.
+ Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
+ new resource has been created.
This header field shall be present if the response status code is 201 or 3xx.
- In the present document this header field is also used if the response status code is 202 and a new resource was created.
- WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization token.
- Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for certain resources.
- Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the response, and the total length of the file.
+ In the present document this header field is also used if the response status code is 202 and a new resource was
+ created.
+ WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
+ provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
+ token.
+ Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
+ certain resources.
+ Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
+ response, and the total length of the file.
Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
-
- or
-
- 120 Used to indicate how long the user agent ought to wait before making a follow-up request.
- It can be used with 503 responses.
- The value of this field can be an HTTP-date or a number of seconds to delay after the response is received.
-
- #TODO http header for partial uploads: Content-Range: "bytes 0-1199/15000". Id is returned first time and send in following chunks
"""
def __init__(self):
self.instance += 1
self.engine = Engine()
+ self.authenticator = Authenticator()
self.valid_methods = { # contains allowed URL and methods
"admin": {
"v1": {
"tokens": {"METHODS": ("GET", "POST", "DELETE"),
- "<ID>": { "METHODS": ("GET", "DELETE")}
- },
+ "<ID>": {"METHODS": ("GET", "DELETE")}
+ },
"users": {"METHODS": ("GET", "POST"),
- "<ID>": {"METHODS": ("GET", "POST", "DELETE")}
- },
+ "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT")}
+ },
"projects": {"METHODS": ("GET", "POST"),
- "<ID>": {"METHODS": ("GET", "DELETE")}
- },
+ # Added PUT to allow Project Name modification
+ "<ID>": {"METHODS": ("GET", "DELETE", "PUT")}
+ },
+ "roles": {"METHODS": ("GET", "POST"),
+ "<ID>": {"METHODS": ("GET", "POST", "DELETE")}
+ },
"vims": {"METHODS": ("GET", "POST"),
- "<ID>": {"METHODS": ("GET", "DELETE")}
- },
+ "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
+ },
"vim_accounts": {"METHODS": ("GET", "POST"),
- "<ID>": {"METHODS": ("GET", "DELETE")}
- },
+ "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
+ },
+ "wim_accounts": {"METHODS": ("GET", "POST"),
+ "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
+ },
"sdns": {"METHODS": ("GET", "POST"),
- "<ID>": {"METHODS": ("GET", "DELETE")}
- },
+ "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
+ },
+ }
+ },
+ "pdu": {
+ "v1": {
+ "pdu_descriptors": {"METHODS": ("GET", "POST"),
+ "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT")}
+ },
}
},
"nsd": {
"v1": {
- "ns_descriptors_content": { "METHODS": ("GET", "POST"),
- "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
- },
- "ns_descriptors": { "METHODS": ("GET", "POST"),
- "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH",
- "nsd_content": { "METHODS": ("GET", "PUT")},
- "nsd": {"METHODS": "GET"}, # descriptor inside package
- "artifacts": {"*": {"METHODS": "GET"}}
- }
-
- },
+ "ns_descriptors_content": {"METHODS": ("GET", "POST"),
+ "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
+ },
+ "ns_descriptors": {"METHODS": ("GET", "POST"),
+ "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
+ "nsd_content": {"METHODS": ("GET", "PUT")},
+ "nsd": {"METHODS": "GET"}, # descriptor inside package
+ "artifacts": {"*": {"METHODS": "GET"}}
+ }
+ },
"pnf_descriptors": {"TODO": ("GET", "POST"),
- "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
- "pnfd_content": {"TODO": ("GET", "PUT")}
- }
- },
+ "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
+ "pnfd_content": {"TODO": ("GET", "PUT")}
+ }
+ },
"subscriptions": {"TODO": ("GET", "POST"),
- "<ID>": {"TODO": ("GET", "DELETE"),}
- },
+ "<ID>": {"TODO": ("GET", "DELETE")}
+ },
}
},
"vnfpkgm": {
"v1": {
- "vnf_packages_content": { "METHODS": ("GET", "POST"),
- "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
- },
- "vnf_packages": { "METHODS": ("GET", "POST"),
- "<ID>": { "METHODS": ("GET", "DELETE"), "TODO": "PATCH", # GET: vnfPkgInfo
- "package_content": { "METHODS": ("GET", "PUT"), # package
- "upload_from_uri": {"TODO": "POST"}
- },
- "vnfd": {"METHODS": "GET"}, # descriptor inside package
- "artifacts": {"*": {"METHODS": "GET"}}
- }
-
- },
+ "vnf_packages_content": {"METHODS": ("GET", "POST"),
+ "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
+ },
+ "vnf_packages": {"METHODS": ("GET", "POST"),
+ "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
+ "package_content": {"METHODS": ("GET", "PUT"), # package
+ "upload_from_uri": {"TODO": "POST"}
+ },
+ "vnfd": {"METHODS": "GET"}, # descriptor inside package
+ "artifacts": {"*": {"METHODS": "GET"}}
+ }
+ },
"subscriptions": {"TODO": ("GET", "POST"),
- "<ID>": {"TODO": ("GET", "DELETE"),}
- },
+ "<ID>": {"TODO": ("GET", "DELETE")}
+ },
}
},
"nslcm": {
"v1": {
"ns_instances_content": {"METHODS": ("GET", "POST"),
- "<ID>": {"METHODS": ("GET", "DELETE")}
- },
+ "<ID>": {"METHODS": ("GET", "DELETE")}
+ },
"ns_instances": {"METHODS": ("GET", "POST"),
- "<ID>": {"TODO": ("GET", "DELETE"),
- "scale": {"TODO": "POST"},
- "terminate": {"METHODS": "POST"},
- "instantiate": {"METHODS": "POST"},
- "action": {"METHODS": "POST"},
- }
- },
+ "<ID>": {"METHODS": ("GET", "DELETE"),
+ "scale": {"METHODS": "POST"},
+ "terminate": {"METHODS": "POST"},
+ "instantiate": {"METHODS": "POST"},
+ "action": {"METHODS": "POST"},
+ }
+ },
"ns_lcm_op_occs": {"METHODS": "GET",
- "<ID>": {"METHODS": "GET"},
- },
+ "<ID>": {"METHODS": "GET"},
+ },
"vnfrs": {"METHODS": ("GET"),
- "<ID>": {"METHODS": ("GET")}
- },
+ "<ID>": {"METHODS": ("GET")}
+ },
+ "vnf_instances": {"METHODS": ("GET"),
+ "<ID>": {"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"},
+ },
}
},
+ "nspm": {
+ "v1": {
+ "pm_jobs": {
+ "<ID>": {
+ "reports": {
+ "<ID>": {"METHODS": ("GET")}
+ }
+ },
+ },
+ },
+ },
}
- def _authorization(self):
- token = None
- user_passwd64 = None
- try:
- # 1. Get token Authorization bearer
- auth = cherrypy.request.headers.get("Authorization")
- if auth:
- auth_list = auth.split(" ")
- if auth_list[0].lower() == "bearer":
- token = auth_list[-1]
- elif auth_list[0].lower() == "basic":
- user_passwd64 = auth_list[-1]
- if not token:
- if cherrypy.session.get("Authorization"):
- # 2. Try using session before request a new token. If not, basic authentication will generate
- token = cherrypy.session.get("Authorization")
- if token == "logout":
- token = None # force Unauthorized response to insert user pasword again
- elif user_passwd64 and cherrypy.request.config.get("auth.allow_basic_authentication"):
- # 3. Get new token from user password
- user = None
- passwd = None
- try:
- user_passwd = standard_b64decode(user_passwd64).decode()
- user, _, passwd = user_passwd.partition(":")
- except:
- pass
- outdata = self.engine.new_token(None, {"username": user, "password": passwd})
- token = outdata["id"]
- cherrypy.session['Authorization'] = token
- # 4. Get token from cookie
- # if not token:
- # auth_cookie = cherrypy.request.cookie.get("Authorization")
- # if auth_cookie:
- # token = auth_cookie.value
- return self.engine.authorize(token)
- except EngineException as e:
- if cherrypy.session.get('Authorization'):
- del cherrypy.session['Authorization']
- cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="{}"'.format(e)
- raise
-
def _format_in(self, kwargs):
try:
indata = None
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 = {}
elif format_yaml:
try:
kwargs[k] = yaml.load(v)
- except:
+ except Exception:
pass
elif k.endswith(".gt") or k.endswith(".lt") or k.endswith(".gte") or k.endswith(".lte"):
try:
kwargs[k] = int(v)
- except:
+ except Exception:
try:
kwargs[k] = float(v)
- except:
+ except Exception:
pass
elif v.find(",") > 0:
kwargs[k] = v.split(",")
elif format_yaml:
try:
v[index] = yaml.load(v[index])
- except:
+ except Exception:
pass
return indata
raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
except KeyError as exc:
raise NbiException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
+ except Exception as exc:
+ raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
@staticmethod
def _format_out(data, session=None, _format=None):
elif "application/yaml" in accept or "*/*" in accept or "text/plain" in accept:
pass
- else:
+ # 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")
session = None
try:
if cherrypy.request.method == "GET":
- session = self._authorization()
+ session = self.authenticator.authorize()
outdata = "Index page"
else:
raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value,
- "Method {} not allowed for tokens".format(cherrypy.request.method))
+ "Method {} not allowed for tokens".format(cherrypy.request.method))
return self._format_out(outdata, session)
- except EngineException as e:
+ except (EngineException, AuthException) as e:
cherrypy.log("index Exception {}".format(e))
cherrypy.response.status = e.http_code.value
return self._format_out("Welcome to OSM!", session)
raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST)
try:
if method == "GET":
- session = self._authorization()
+ session = self.authenticator.authorize()
if token_id:
- outdata = self.engine.get_token(session, token_id)
+ outdata = self.authenticator.get_token(session, token_id)
else:
- outdata = self.engine.get_token_list(session)
+ outdata = self.authenticator.get_token_list(session)
elif method == "POST":
try:
- session = self._authorization()
- except:
+ session = self.authenticator.authorize()
+ except Exception:
session = None
if kwargs:
indata.update(kwargs)
- outdata = self.engine.new_token(session, indata, cherrypy.request.remote)
+ outdata = self.authenticator.new_token(session, indata, cherrypy.request.remote)
session = outdata
cherrypy.session['Authorization'] = outdata["_id"]
self._set_location_header("admin", "v1", "tokens", outdata["_id"])
if not token_id and "id" in kwargs:
token_id = kwargs["id"]
elif not token_id:
- session = self._authorization()
+ session = self.authenticator.authorize()
token_id = session["_id"]
- outdata = self.engine.del_token(token_id)
- oudata = None
+ outdata = self.authenticator.del_token(token_id)
session = None
cherrypy.session['Authorization'] = "logout"
# cherrypy.response.cookie["Authorization"] = token_id
else:
raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
return self._format_out(outdata, session)
- except (NbiException, EngineException, DbException) as e:
+ except (NbiException, EngineException, DbException, AuthException) as e:
cherrypy.log("tokens Exception {}".format(e))
cherrypy.response.status = e.http_code.value
problem_details = {
def test(self, *args, **kwargs):
thread_info = None
if args and args[0] == "help":
- return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nprune\nlogin\nlogin2\n"\
- "sleep/<time>\nmessage/topic\n</pre></html>"
+ return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
+ "sleep/<time>\nmessage/topic\n</pre></html>"
elif args and args[0] == "init":
try:
return f
elif len(args) == 2 and args[0] == "db-clear":
- return self.engine.del_item_list({"project_id": "admin"}, args[1], {})
- elif args and args[0] == "prune":
- return self.engine.prune()
+ deleted_info = self.engine.db.del_list(args[1], kwargs)
+ return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
+ elif len(args) and args[0] == "fs-clear":
+ if len(args) >= 2:
+ folders = (args[1],)
+ else:
+ folders = self.engine.fs.dir_ls(".")
+ for folder in folders:
+ self.engine.fs.file_delete(folder)
+ 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"'
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)
" session: {}\n".format(cherrypy.session) +
" cookie: {}\n".format(cherrypy.request.cookie) +
" method: {}\n".format(cherrypy.request.method) +
- " session: {}\n".format(cherrypy.session.get('fieldname')) +
+ " session: {}\n".format(cherrypy.session.get('fieldname')) +
" body:\n")
return_text += " length: {}\n".format(cherrypy.request.body.length)
if cherrypy.request.body.length:
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:
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)
- elif "METHODS" in reference and not method in reference["METHODS"]:
+ elif "METHODS" in reference and method not in reference["METHODS"]:
raise NbiException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
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
+ @staticmethod
+ def _manage_admin_query(session, kwargs, method, _id):
+ """
+ Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
+ Check that users has rights to use them and returs the admin_query
+ :param session: session rights obtained by token
+ :param kwargs: query string input.
+ :param method: http method: GET, POSST, PUT, ...
+ :param _id:
+ :return: admin_query dictionary with keys:
+ public: True, False or None
+ force: True or False
+ project_id: tuple with projects used for accessing an element
+ set_project: tuple with projects that a created element will belong to
+ method: show, list, delete, write
+ """
+ admin_query = {"force": False, "project_id": (session["project_id"], ), "username": session["username"],
+ "admin": session["admin"], "public": None}
+ if kwargs:
+ # FORCE
+ if "FORCE" in kwargs:
+ 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
+ admin_query["public"] = True
+ else:
+ admin_query["public"] = False
+ del kwargs["PUBLIC"]
+ # ADMIN
+ if "ADMIN" in kwargs:
+ behave_as = kwargs.pop("ADMIN")
+ if behave_as.lower() != "false":
+ if not session["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
+ 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, )
+ 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, )
+ 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)
+ admin_query["set_project"] = set_project
+
+ # PROJECT_READ
+ # if "PROJECT_READ" in kwargs:
+ # admin_query["project"] = kwargs.pop("project")
+ # if admin_query["project"] == session["project_id"]:
+ if method == "GET":
+ if _id:
+ admin_query["method"] = "show"
+ else:
+ admin_query["method"] = "list"
+ elif method == "DELETE":
+ admin_query["method"] = "delete"
+ else:
+ admin_query["method"] = "write"
+ return admin_query
+
@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
- rollback = 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", "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)
else:
method = cherrypy.request.method
- self._check_valid_url_method(method, topic, version, item, _id, item2, *args)
+ self._check_valid_url_method(method, main_topic, version, topic, _id, item, *args)
- if topic == "admin" and item == "tokens":
+ if main_topic == "admin" and topic == "tokens":
return self.token(method, _id, kwargs)
# self.engine.load_dbase(cherrypy.request.app.config)
- session = self._authorization()
+ session = self.authenticator.authorize()
+ session = self._manage_admin_query(session, kwargs, method, _id)
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":
- engine_item = "vnfrs"
- 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 and topic != "pm_jobs":
+ 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 be removed 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,
- cherrypy.request.headers.get("Accept"))
+ 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)
+ if item == "reports":
+ # TODO check that project_id (_id in this context) has permissions
+ _id = args[0]
+ 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(session, engine_item, {}, None, cherrypy.request.headers)
- rollback = {"session": session, "item": engine_item, "_id": _id, "force": True}
- completed = self.engine.upload_content(session, engine_item, _id, indata, kwargs, cherrypy.request.headers)
+ _id = self.engine.new_item(rollback, session, engine_topic, {}, None, cherrypy.request.headers)
+ completed = self.engine.upload_content(session, engine_topic, _id, indata, kwargs,
+ cherrypy.request.headers)
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(session, engine_item, indata, kwargs)
- rollback = {"session": session, "item": engine_item, "_id": _id, "force": True}
- self.engine.ns_action(session, _id, "instantiate", {}, 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)
+ # 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 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}
- elif item == "ns_instances" and item2:
- _id = self.engine.ns_action(session, _id, item2, indata, kwargs)
- self._set_location_header(topic, version, "ns_lcm_op_occs", _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)
+ 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(session, engine_item, indata, kwargs, cherrypy.request.headers)
- self._set_location_header(topic, version, item, _id)
+ _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs,
+ cherrypy.request.headers)
+ 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":
- opp_id = self.engine.ns_action(session, _id, "terminate", {"autoremove": True}, None)
- outdata = {"_id": opp_id}
- cherrypy.response.status = HTTPStatus.ACCEPTED.value
- else:
- force = kwargs.get("FORCE")
- self.engine.del_item(session, engine_item, _id, force)
+ delete_in_process = False
+ if topic == "ns_instances_content" and not session["force"]:
+ nslcmop_desc = {
+ "lcmOperationType": "terminate",
+ "nsInstanceId": _id,
+ "autoremove": True
+ }
+ opp_id = self.engine.new_item(rollback, 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 session["force"]:
+ nsilcmop_desc = {
+ "lcmOperationType": "terminate",
+ "nsiInstanceId": _id,
+ "autoremove": True
+ }
+ opp_id = self.engine.new_item(rollback, 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(session, engine_topic, _id)
cherrypy.response.status = HTTPStatus.NO_CONTENT.value
- if engine_item in ("vim_accounts", "sdns"):
+ if engine_topic in ("vim_accounts", "wim_accounts", "sdns"):
cherrypy.response.status = HTTPStatus.ACCEPTED.value
- elif method == "PUT":
- if not indata and not kwargs:
+ elif method in ("PUT", "PATCH"):
+ outdata = None
+ if not indata and not kwargs and not session.get("set_project"):
raise NbiException("Nothing to update. Provide payload and/or query string",
HTTPStatus.BAD_REQUEST)
- if item2 in ("nsd_content", "package_content"):
- 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)
if not completed:
cherrypy.response.headers["Transaction-Id"] = id
- cherrypy.response.status = HTTPStatus.NO_CONTENT.value
- outdata = None
else:
- outdata = {"id": self.engine.edit_item(session, engine_item, args[1], indata, kwargs)}
+ self.engine.edit_item(session, engine_topic, _id, indata, kwargs)
+ 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) 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), traceback=True)
+ http_code_name = HTTPStatus.BAD_REQUEST.name
if hasattr(outdata, "close"): # is an open file
outdata.close()
- if rollback:
+ error_text = str(e)
+ rollback.reverse()
+ for rollback_item in rollback:
try:
- self.engine.del_item(**rollback)
+ 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.db.del_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
+ fail_on_empty=False)
except Exception as e2:
- cherrypy.log("Rollback Exception {}: {}".format(rollback, 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))
-# def validate_password(realm, username, password):
-# cherrypy.log("realm "+ str(realm))
-# if username == "admin" and password == "admin":
-# return True
-# return False
-
-
def _start_service():
"""
Callback function called when cherrypy.engine starts
Set database, storage, message configuration
Init database with admin/admin user password
"""
+ global nbi_server
+ global subscription_thread
cherrypy.log.error("Starting osm_nbi")
# update general cherrypy configuration
update_dict = {}
for k, v in environ.items():
if not k.startswith("OSMNBI_"):
continue
- k1, _, k2 = k[7:].lower().partition("_")
+ k1, _, k2 = k[7:].lower().partition("_")
if not k2:
continue
try:
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 == "server":
- update_dict['server' + k2] = v
- # TODO add more entries
- elif k1 in ("message", "database", "storage"):
- if k2 == "port":
+ elif k1 in ("server", "test", "auth", "log"):
+ update_dict[k1 + '.' + k2] = v
+ elif k1 in ("message", "database", "storage", "authentication"):
+ # k2 = k2.replace('_', '.')
+ if k2 in ("port", "db_port"):
engine_config[k1][k2] = int(v)
else:
engine_config[k1][k2] = v
+
except ValueError as e:
cherrypy.log.error("Ignoring environ '{}': " + str(e))
except Exception as e:
if update_dict:
cherrypy.config.update(update_dict)
+ engine_config["global"].update(update_dict)
# logging cherrypy
log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
logger_cherry = logging.getLogger("cherrypy")
logger_nbi = logging.getLogger("nbi")
- if "logfile" in engine_config["global"]:
- file_handler = logging.handlers.RotatingFileHandler(engine_config["global"]["logfile"],
+ 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.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)
-
- if engine_config["global"].get("loglevel"):
- logger_cherry.setLevel(engine_config["global"]["loglevel"])
- logger_nbi.setLevel(engine_config["global"]["loglevel"])
+ # 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"])
+ 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():
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)
+ 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)
+
+ # start subscriptions thread:
+ 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:
- cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version)
- except EngineException:
+ with open("{}/version".format(engine_config["/static"]['tools.staticdir.dir'])) as version_file:
+ version_data = version_file.read()
+ cherrypy.log.error("Starting OSM NBI Version: {}".format(version_data.replace("\n", " ")))
+ except Exception:
pass
- # getenv('OSMOPENMANO_TENANT', None)
def _stop_service():
Callback function called when cherrypy.engine stops
TODO: Ending database connections.
"""
+ global subscription_thread
+ if subscription_thread:
+ subscription_thread.terminate()
+ subscription_thread = None
cherrypy.tree.apps['/osm'].root.engine.stop()
cherrypy.log.error("Stopping osm_nbi")
-def nbi():
+
+def nbi(config_file):
+ global nbi_server
# conf = {
# '/': {
# #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
# cherrypy.config.update({'tools.auth_basic.on': True,
# '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(Server(), '/osm', "nbi.cfg")
+ cherrypy.quickstart(nbi_server, '/osm', config_file)
+
+
+def usage():
+ print("""Usage: {} [options]
+ -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
+ -h|--help: shows this help
+ """.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__':
- nbi()
+ try:
+ # load parameters and configuration
+ opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
+ # TODO add "log-socket-host=", "log-socket-port=", "log-file="
+ config_file = None
+ for o, a in opts:
+ if o in ("-h", "--help"):
+ usage()
+ sys.exit()
+ elif o in ("-c", "--config"):
+ config_file = a
+ # elif o == "--log-socket-port":
+ # log_socket_port = a
+ # elif o == "--log-socket-host":
+ # log_socket_host = a
+ # elif o == "--log-file":
+ # log_file = a
+ else:
+ 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)
+ exit(1)
+ else:
+ 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)
+ exit(1)
+ nbi(config_file)
+ except getopt.GetoptError as e:
+ print(str(e), file=sys.stderr)
+ # usage()
+ exit(1)