Feature 10922: Stop, start and rebuild
[osm/RO.git] / NG-RO / osm_ng_ro / ro_main.py
index 768f205..bd2ea9e 100644 (file)
 # limitations under the License.
 ##
 
-import cherrypy
-import time
+
+from codecs import getreader
+import getopt
+from http import HTTPStatus
 import json
-import yaml
-import osm_ng_ro.html_out as html
 import logging
 import logging.handlers
-import getopt
+from os import environ, path
 import sys
+import time
 
-from osm_ng_ro.ns import Ns, NsException
-from osm_ng_ro.validation import ValidationError
-from osm_ng_ro.vim_admin import VimAdminThread
+import cherrypy
 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_ng_ro import version as ro_version, version_date as ro_version_date
+import osm_ng_ro.html_out as html
+from osm_ng_ro.ns import Ns, NsException
+from osm_ng_ro.validation import ValidationError
+from osm_ng_ro.vim_admin import VimAdminThread
+import yaml
 
-__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 
-__version__ = "0.1."    # file version, not NBI version
+__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
+__version__ = "0.1."  # file version, not NBI version
 version_date = "May 2020"
 
-database_version = '1.2'
-auth_database_version = '1.0'
-ro_server = None           # instance of Server class
-vim_admin_thread = None   # instance of VimAdminThread class
+database_version = "1.2"
+auth_database_version = "1.0"
+ro_server = None  # instance of Server class
+vim_admin_thread = None  # instance of VimAdminThread class
 
 # vim_threads = None  # instance of VimThread class
 
@@ -76,15 +77,36 @@ valid_url_methods = {
             "tokens": {
                 "METHODS": ("POST",),
                 "ROLE_PERMISSION": "tokens:",
-                "<ID>": {
-                    "METHODS": ("DELETE",),
-                    "ROLE_PERMISSION": "tokens:id:"
-                }
+                "<ID>": {"METHODS": ("DELETE",), "ROLE_PERMISSION": "tokens:id:"},
             },
         }
     },
     "ns": {
         "v1": {
+            "rebuild": {
+                "METHODS": ("POST",),
+                "ROLE_PERMISSION": "rebuild:",
+                "<ID>": {
+                    "METHODS": ("POST",),
+                    "ROLE_PERMISSION": "rebuild:id:",
+                },
+            },
+            "start": {
+                "METHODS": ("POST",),
+                "ROLE_PERMISSION": "start:",
+                "<ID>": {
+                    "METHODS": ("POST",),
+                    "ROLE_PERMISSION": "start:id:",
+                },
+            },
+            "stop": {
+                "METHODS": ("POST",),
+                "ROLE_PERMISSION": "stop:",
+                "<ID>": {
+                    "METHODS": ("POST",),
+                    "ROLE_PERMISSION": "stop:id:",
+                },
+            },
             "deploy": {
                 "METHODS": ("GET",),
                 "ROLE_PERMISSION": "deploy:",
@@ -97,9 +119,29 @@ valid_url_methods = {
                         "cancel": {
                             "METHODS": ("POST",),
                             "ROLE_PERMISSION": "deploy:id:id:cancel",
-                        }
-                    }
-                }
+                        },
+                    },
+                },
+            },
+            "recreate": {
+                "<ID>": {
+                    "METHODS": ("POST"),
+                    "ROLE_PERMISSION": "recreate:id:",
+                    "<ID>": {
+                        "METHODS": ("GET",),
+                        "ROLE_PERMISSION": "recreate:id:id:",
+                    },
+                },
+            },
+            "migrate": {
+                "<ID>": {
+                    "METHODS": ("POST"),
+                    "ROLE_PERMISSION": "migrate:id:",
+                    "<ID>": {
+                        "METHODS": ("GET",),
+                        "ROLE_PERMISSION": "migrate:id:id:",
+                    },
+                },
             },
         }
     },
@@ -107,7 +149,6 @@ valid_url_methods = {
 
 
 class RoException(Exception):
-
     def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
         Exception.__init__(self, message)
         self.http_code = http_code
@@ -118,18 +159,15 @@ class AuthException(RoException):
 
 
 class Authenticator:
-    
     def __init__(self, valid_url_methods, valid_query_string):
         self.valid_url_methods = valid_url_methods
         self.valid_query_string = valid_query_string
 
     def authorize(self, *args, **kwargs):
         return {"token": "ok", "id": "ok"}
-    
+
     def new_token(self, token_info, indata, remote):
-        return {"token": "ok",
-                "id": "ok",
-                "remote": remote}
+        return {"token": "ok", "id": "ok", "remote": remote}
 
     def del_token(self, token_id):
         pass
@@ -156,11 +194,18 @@ class Server(object):
             "deploy:id:delete": self.ns.delete,
             "deploy:id:id:get": self.ns.status,
             "deploy:id:id:cancel:post": self.ns.cancel,
+            "rebuild:id:post": self.ns.rebuild_start_stop,
+            "start:id:post": self.ns.rebuild_start_stop,
+            "stop:id:post": self.ns.rebuild_start_stop,
+            "recreate:id:post": self.ns.recreate,
+            "recreate:id:id:get": self.ns.recreate_status,
+            "migrate:id:post": self.ns.migrate,
         }
 
     def _format_in(self, kwargs):
         try:
             indata = None
+
             if cherrypy.request.body.length:
                 error_text = "Invalid input format "
 
@@ -171,32 +216,50 @@ 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, Loader=yaml.SafeLoader)
+                        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 RoException("empty file or content", HTTPStatus.BAD_REQUEST)
+                                raise RoException(
+                                    "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, Loader=yaml.SafeLoader)
+                        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, Loader=yaml.SafeLoader)
                     cherrypy.request.headers.pop("Content-File-MD5", None)
+
             if not indata:
                 indata = {}
 
@@ -213,7 +276,12 @@ class Server(object):
                             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:
@@ -251,55 +319,83 @@ class Server(object):
         :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'
+                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:
+                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='"'
+                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.expose
     def index(self, *args, **kwargs):
         token_info = None
+
         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)
-
         except (NsException, AuthException) as e:
             # cherrypy.log("index Exception {}".format(e))
             cherrypy.response.status = e.http_code.value
+
             return self._format_out("Welcome to OSM!", token_info)
 
     @cherrypy.expose
@@ -307,11 +403,19 @@ class Server(object):
         # TODO consider to remove and provide version using the static version file
         try:
             if cherrypy.request.method != "GET":
-                raise RoException("Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED)
+                raise RoException(
+                    "Only method GET is allowed",
+                    HTTPStatus.METHOD_NOT_ALLOWED,
+                )
             elif args or kwargs:
-                raise RoException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED)
+                raise RoException(
+                    "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_ng_ro_version = {"version": ro_version, "date": ro_version_date}
+
             return self._format_out(osm_ng_ro_version)
         except RoException as e:
             cherrypy.response.status = e.http_code.value
@@ -320,6 +424,7 @@ class Server(object):
                 "status": e.http_code.value,
                 "detail": str(e),
             }
+
             return self._format_out(problem_details, None)
 
     def new_token(self, engine_session, indata, *args, **kwargs):
@@ -329,58 +434,77 @@ class Server(object):
             token_info = self.authenticator.authorize()
         except Exception:
             token_info = None
+
         if kwargs:
             indata.update(kwargs)
+
         # This is needed to log the user when authentication fails
         cherrypy.request.login = "{}".format(indata.get("username", "-"))
-        token_info = self.authenticator.new_token(token_info, indata, cherrypy.request.remote)
-        cherrypy.session['Authorization'] = token_info["id"]
+        token_info = self.authenticator.new_token(
+            token_info, indata, cherrypy.request.remote
+        )
+        cherrypy.session["Authorization"] = token_info["id"]
         self._set_location_header("admin", "v1", "tokens", token_info["id"])
         # for logging
 
         # cherrypy.response.cookie["Authorization"] = outdata["id"]
         # cherrypy.response.cookie["Authorization"]['expires'] = 3600
+
         return token_info, token_info["id"], True
 
     def del_token(self, engine_session, indata, version, _id, *args, **kwargs):
         token_id = _id
+
         if not token_id and "id" in kwargs:
             token_id = kwargs["id"]
         elif not token_id:
             token_info = self.authenticator.authorize()
             # for logging
             token_id = token_info["id"]
+
         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
+
         return None, None, True
-    
+
     @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"):
+        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 "<html><pre>\ninit\nfile/<name>  download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
-                   "sleep/<time>\nmessage/topic\n</pre></html>"
 
+        if args and args[0] == "help":
+            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:
                 # self.ns.load_dbase(cherrypy.request.app.config)
                 self.ns.create_admin()
+
                 return "Done. User 'admin', password 'admin' created"
             except Exception:
                 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['/ro'].config["storage"]["path"] + "/" + args[1],
-                                                  "text/plain", "attachment")
+            return cherrypy.lib.static.serve_file(
+                cherrypy.tree.apps["/ro"].config["storage"]["path"] + "/" + args[1],
+                "text/plain",
+                "attachment",
+            )
         elif args and args[0] == "file2":
-            f_path = cherrypy.tree.apps['/ro'].config["storage"]["path"] + "/" + args[1]
+            f_path = cherrypy.tree.apps["/ro"].config["storage"]["path"] + "/" + args[1]
             f = open(f_path, "r")
             cherrypy.response.headers["Content-type"] = "text/plain"
             return f
@@ -393,24 +517,32 @@ class Server(object):
                 folders = (args[1],)
             else:
                 folders = self.ns.fs.dir_ls(".")
+
             for folder in folders:
                 self.ns.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"'
+                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
+
             try:
                 sleep_time = int(args[1])
             except Exception:
                 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
                 return self._format_out("Database already initialized")
+
             thread_info = cherrypy.thread_data
             print(thread_info)
             time.sleep(sleep_time)
@@ -418,53 +550,76 @@ class Server(object):
         elif len(args) >= 2 and args[0] == "message":
             main_topic = args[1]
             return_text = "<html><pre>{} ->\n".format(main_topic)
+
             try:
-                if cherrypy.request.method == 'POST':
+                if cherrypy.request.method == "POST":
                     to_send = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
                     for k, v in to_send.items():
                         self.ns.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.ns.msg.write(main_topic, k, yaml.load(v, Loader=yaml.SafeLoader))
-                        return_text += "  {}: {}\n".format(k, yaml.load(v, Loader=yaml.SafeLoader))
+                        self.ns.msg.write(
+                            main_topic, k, yaml.load(v, Loader=yaml.SafeLoader)
+                        )
+                        return_text += "  {}: {}\n".format(
+                            k, yaml.load(v, Loader=yaml.SafeLoader)
+                        )
             except Exception as e:
                 return_text += "Error: " + str(e)
+
             return_text += "</pre></html>\n"
+
             return return_text
 
         return_text = (
-            "<html><pre>\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")
+            "<html><pre>\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 += "</pre></html>"
+
         return return_text
 
     @staticmethod
     def _check_valid_url_method(method, *args):
         if len(args) < 3:
-            raise RoException("URL must contain at least 'main_topic/version/topic'", HTTPStatus.METHOD_NOT_ALLOWED)
+            raise RoException(
+                "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 RoException("URL contains unexpected extra items '{}'".format(arg),
-                                  HTTPStatus.METHOD_NOT_ALLOWED)
+                raise RoException(
+                    "URL contains unexpected extra items '{}'".format(arg),
+                    HTTPStatus.METHOD_NOT_ALLOWED,
+                )
 
             if arg in reference:
                 reference = reference[arg]
@@ -474,11 +629,22 @@ class Server(object):
                 # reference = reference["*"]
                 break
             else:
-                raise RoException("Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED)
+                raise RoException(
+                    "Unexpected URL item {}".format(arg),
+                    HTTPStatus.METHOD_NOT_ALLOWED,
+                )
+
         if "TODO" in reference and method in reference["TODO"]:
-            raise RoException("Method {} not supported yet for this URL".format(method), HTTPStatus.NOT_IMPLEMENTED)
+            raise RoException(
+                "Method {} not supported yet for this URL".format(method),
+                HTTPStatus.NOT_IMPLEMENTED,
+            )
         elif "METHODS" not in reference or method not in reference["METHODS"]:
-            raise RoException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
+            raise RoException(
+                "Method {} not supported for this URL".format(method),
+                HTTPStatus.METHOD_NOT_ALLOWED,
+            )
+
         return reference["ROLE_PERMISSION"] + method.lower()
 
     @staticmethod
@@ -492,71 +658,137 @@ 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"] = "/ro/{}/{}/{}/{}".format(main_topic, version, topic, id)
+        cherrypy.response.headers["Location"] = "/ro/{}/{}/{}/{}".format(
+            main_topic, version, topic, id
+        )
+
         return
 
     @cherrypy.expose
-    def default(self, main_topic=None, version=None, topic=None, _id=None, _id2=None, *args, **kwargs):
+    def default(
+        self,
+        main_topic=None,
+        version=None,
+        topic=None,
+        _id=None,
+        _id2=None,
+        *args,
+        **kwargs,
+    ):
         token_info = None
         outdata = None
         _format = None
         method = "DONE"
         rollback = []
         engine_session = None
+
         try:
             if not main_topic or not version or not topic:
-                raise RoException("URL must contain at least 'main_topic/version/topic'",
-                                  HTTPStatus.METHOD_NOT_ALLOWED)
-            if main_topic not in ("admin", "ns",):
-                raise RoException("URL main_topic '{}' not supported".format(main_topic),
-                                  HTTPStatus.METHOD_NOT_ALLOWED)
-            if version != 'v1':
-                raise RoException("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 RoException(
+                    "URL must contain at least 'main_topic/version/topic'",
+                    HTTPStatus.METHOD_NOT_ALLOWED,
+                )
+
+            if main_topic not in (
+                "admin",
+                "ns",
+            ):
+                raise RoException(
+                    "URL main_topic '{}' not supported".format(main_topic),
+                    HTTPStatus.METHOD_NOT_ALLOWED,
+                )
+
+            if version != "v1":
+                raise RoException(
+                    "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, _id2, *args,
-                                                           **kwargs)
+            role_permission = self._check_valid_url_method(
+                method, main_topic, version, topic, _id, _id2, *args, **kwargs
+            )
             # skip token validation if requesting a token
             indata = self._format_in(kwargs)
+
             if main_topic != "admin" or topic != "tokens":
                 token_info = self.authenticator.authorize(role_permission, _id)
+
             outdata, created_id, done = self.map_operation[role_permission](
-                engine_session, indata, version, _id, _id2, *args, *kwargs)
+                engine_session, indata, version, _id, _id2, *args, *kwargs
+            )
+
             if created_id:
                 self._set_location_header(main_topic, version, topic, _id)
-            cherrypy.response.status = HTTPStatus.ACCEPTED.value if not done else HTTPStatus.OK.value if \
-                outdata is not None else HTTPStatus.NO_CONTENT.value
+
+            cherrypy.response.status = (
+                HTTPStatus.ACCEPTED.value
+                if not done
+                else HTTPStatus.OK.value
+                if outdata is not None
+                else HTTPStatus.NO_CONTENT.value
+            )
+
             return self._format_out(outdata, token_info, _format)
         except Exception as e:
-            if isinstance(e, (RoException, NsException, DbException, FsException, MsgException, AuthException,
-                              ValidationError)):
+            if isinstance(
+                e,
+                (
+                    RoException,
+                    NsException,
+                    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
+                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()
+
             error_text = str(e)
             rollback.reverse()
+
             for rollback_item in rollback:
                 try:
                     if rollback_item.get("operation") == "set":
-                        self.ns.db.set_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
-                                           rollback_item["content"], fail_on_empty=False)
+                        self.ns.db.set_one(
+                            rollback_item["topic"],
+                            {"_id": rollback_item["_id"]},
+                            rollback_item["content"],
+                            fail_on_empty=False,
+                        )
                     else:
-                        self.ns.db.del_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
-                                           fail_on_empty=False)
+                        self.ns.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):
             #     error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
             #         engine_topic[:-1], method, error_text)
@@ -565,6 +797,7 @@ class Server(object):
                 "status": http_code_value,
                 "detail": error_text,
             }
+
             return self._format_out(problem_details, token_info)
             # raise cherrypy.HTTPError(e.http_code.value, str(e))
         finally:
@@ -572,7 +805,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():
@@ -587,24 +822,27 @@ def _start_service():
     cherrypy.log.error("Starting osm_ng_ro")
     # update general cherrypy configuration
     update_dict = {}
+    engine_config = cherrypy.tree.apps["/ro"].config
 
-    engine_config = cherrypy.tree.apps['/ro'].config
     for k, v in environ.items():
         if not k.startswith("OSMRO_"):
             continue
+
         k1, _, k2 = k[6:].lower().partition("_")
+
         if not k2:
             continue
+
         try:
             if k1 in ("server", "test", "auth", "log"):
                 # update [global] configuration
-                update_dict[k1 + '.' + k2] = yaml.safe_load(v)
+                update_dict[k1 + "." + k2] = yaml.safe_load(v)
             elif k1 == "static":
                 # update [/static] configuration
                 engine_config["/static"]["tools.staticdir." + k2] = yaml.safe_load(v)
             elif k1 == "tools":
                 # update [/] configuration
-                engine_config["/"]["tools." + k2.replace('_', '.')] = yaml.safe_load(v)
+                engine_config["/"]["tools." + k2.replace("_", ".")] = yaml.safe_load(v)
             elif k1 in ("message", "database", "storage", "authentication"):
                 engine_config[k1][k2] = yaml.safe_load(v)
 
@@ -616,51 +854,68 @@ 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("ro")
+    logger = logging.getLogger("ro")
 
     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)
+        logger.addHandler(file_handler)
+
     # log always to standard output
-    for format_, logger in {"ro.server %(filename)s:%(lineno)s": logger_server,
-                            "ro.access %(filename)s:%(lineno)s": logger_access,
-                            "%(name)s %(filename)s:%(lineno)s": logger_nbi
-                            }.items():
+    for format_, logger in {
+        "ro.server %(filename)s:%(lineno)s": logger_server,
+        "ro.access %(filename)s:%(lineno)s": logger_access,
+        "%(name)s %(filename)s:%(lineno)s": logger,
+    }.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)
 
     if engine_config["global"].get("log.level"):
         logger_cherry.setLevel(engine_config["global"]["log.level"])
-        logger_nbi.setLevel(engine_config["global"]["log.level"])
+        logger.setLevel(engine_config["global"]["log.level"])
+
     # logging other modules
-    for k1, logname in {"message": "ro.msg", "database": "ro.db", "storage": "ro.fs"}.items():
+    for k1, logname in {
+        "message": "ro.msg",
+        "database": "ro.db",
+        "storage": "ro.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
 
     engine_config["assignment"] = {}
     # ^ each VIM, SDNc will be assigned one worker id. Ns class will add items and VimThread will auto-assign
-    cherrypy.tree.apps['/ro'].root.ns.start(engine_config)
-    cherrypy.tree.apps['/ro'].root.authenticator.start(engine_config)
-    cherrypy.tree.apps['/ro'].root.ns.init_db(target_version=database_version)
+    cherrypy.tree.apps["/ro"].root.ns.start(engine_config)
+    cherrypy.tree.apps["/ro"].root.authenticator.start(engine_config)
+    cherrypy.tree.apps["/ro"].root.ns.init_db(target_version=database_version)
 
     # # start subscriptions thread:
     vim_admin_thread = VimAdminThread(config=engine_config, engine=ro_server.ns)
@@ -678,37 +933,45 @@ def _stop_service():
     TODO: Ending database connections.
     """
     global vim_admin_thread
+
     # terminate vim_admin_thread
     if vim_admin_thread:
         vim_admin_thread.terminate()
+
     vim_admin_thread = None
-    cherrypy.tree.apps['/ro'].root.ns.stop()
+    cherrypy.tree.apps["/ro"].root.ns.stop()
     cherrypy.log.error("Stopping osm_ng_ro")
 
 
 def ro_main(config_file):
     global ro_server
+
     ro_server = Server()
-    cherrypy.engine.subscribe('start', _start_service)
-    cherrypy.engine.subscribe('stop', _stop_service)
-    cherrypy.quickstart(ro_server, '/ro', config_file)
+    cherrypy.engine.subscribe("start", _start_service)
+    cherrypy.engine.subscribe("stop", _stop_service)
+    cherrypy.quickstart(ro_server, "/ro", config_file)
 
 
 def usage():
-    print("""Usage: {} [options]
+    print(
+        """Usage: {} [options]
         -c|--config [configuration_file]: loads the configuration file (default: ./ro.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"])
         # TODO add  "log-socket-host=", "log-socket-port=", "log-file="
         config_file = None
+
         for o, a in opts:
             if o in ("-h", "--help"):
                 usage()
@@ -717,17 +980,29 @@ if __name__ == '__main__':
                 config_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)
+                print(
+                    "configuration file '{}' that not exist".format(config_file),
+                    file=sys.stderr,
+                )
                 exit(1)
         else:
-            for config_file in (path.dirname(__file__) + "/ro.cfg", "./ro.cfg", "/etc/osm/ro.cfg"):
+            for config_file in (
+                path.dirname(__file__) + "/ro.cfg",
+                "./ro.cfg",
+                "/etc/osm/ro.cfg",
+            ):
                 if path.isfile(config_file):
                     break
             else:
-                print("No configuration file 'ro.cfg' found neither at local folder nor at /etc/osm/", file=sys.stderr)
+                print(
+                    "No configuration file 'ro.cfg' found neither at local folder nor at /etc/osm/",
+                    file=sys.stderr,
+                )
                 exit(1)
+
         ro_main(config_file)
     except KeyboardInterrupt:
         print("KeyboardInterrupt. Finishing", file=sys.stderr)