eb5ff6c8d5e27a0a958c4d29f9e1e12e3e4c92ed
2 # -*- coding: utf-8 -*-
5 # Copyright 2020 Telefonica Investigacion y Desarrollo, S.A.U.
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
17 # See the License for the specific language governing permissions and
18 # limitations under the License.
22 from codecs
import getreader
24 from http
import HTTPStatus
27 import logging
.handlers
28 from os
import environ
, path
33 from osm_common
.dbbase
import DbException
34 from osm_common
.fsbase
import FsException
35 from osm_common
.msgbase
import MsgException
36 from osm_ng_ro
import version
as ro_version
, version_date
as ro_version_date
37 import osm_ng_ro
.html_out
as html
38 from osm_ng_ro
.ns
import Ns
, NsException
39 from osm_ng_ro
.validation
import ValidationError
40 from osm_ng_ro
.vim_admin
import VimAdminThread
44 __author__
= "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
45 __version__
= "0.1." # file version, not NBI version
46 version_date
= "May 2020"
48 database_version
= "1.2"
49 auth_database_version
= "1.0"
50 ro_server
= None # instance of Server class
51 vim_admin_thread
= None # instance of VimAdminThread class
53 # vim_threads = None # instance of VimThread class
56 RO North Bound Interface
57 URL: /ro GET POST PUT DELETE PATCH
65 valid_query_string
= ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
66 # ^ Contains possible administrative query string words:
67 # ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
68 # (not owned by my session project).
69 # PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
70 # FORCE=True(by default)|False: Force edition/deletion operations
71 # SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
74 # contains allowed URL and methods, and the role_permission name
79 "ROLE_PERMISSION": "tokens:",
80 "<ID>": {"METHODS": ("DELETE",), "ROLE_PERMISSION": "tokens:id:"},
88 "ROLE_PERMISSION": "deploy:",
90 "METHODS": ("GET", "POST", "DELETE"),
91 "ROLE_PERMISSION": "deploy:id:",
94 "ROLE_PERMISSION": "deploy:id:id:",
97 "ROLE_PERMISSION": "deploy:id:id:cancel",
107 class RoException(Exception):
108 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
109 Exception.__init
__(self
, message
)
110 self
.http_code
= http_code
113 class AuthException(RoException
):
118 def __init__(self
, valid_url_methods
, valid_query_string
):
119 self
.valid_url_methods
= valid_url_methods
120 self
.valid_query_string
= valid_query_string
122 def authorize(self
, *args
, **kwargs
):
123 return {"token": "ok", "id": "ok"}
125 def new_token(self
, token_info
, indata
, remote
):
126 return {"token": "ok", "id": "ok", "remote": remote
}
128 def del_token(self
, token_id
):
131 def start(self
, engine_config
):
135 class Server(object):
137 # to decode bytes to str
138 reader
= getreader("utf-8")
142 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
144 self
.map_operation
= {
145 "token:post": self
.new_token
,
146 "token:id:delete": self
.del_token
,
147 "deploy:get": self
.ns
.get_deploy
,
148 "deploy:id:get": self
.ns
.get_actions
,
149 "deploy:id:post": self
.ns
.deploy
,
150 "deploy:id:delete": self
.ns
.delete
,
151 "deploy:id:id:get": self
.ns
.status
,
152 "deploy:id:id:cancel:post": self
.ns
.cancel
,
155 def _format_in(self
, kwargs
):
159 if cherrypy
.request
.body
.length
:
160 error_text
= "Invalid input format "
162 if "Content-Type" in cherrypy
.request
.headers
:
163 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
164 error_text
= "Invalid json format "
165 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
166 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
167 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
168 error_text
= "Invalid yaml format "
170 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
172 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
174 "application/binary" in cherrypy
.request
.headers
["Content-Type"]
175 or "application/gzip"
176 in cherrypy
.request
.headers
["Content-Type"]
177 or "application/zip" in cherrypy
.request
.headers
["Content-Type"]
178 or "text/plain" in cherrypy
.request
.headers
["Content-Type"]
180 indata
= cherrypy
.request
.body
# .read()
182 "multipart/form-data"
183 in cherrypy
.request
.headers
["Content-Type"]
185 if "descriptor_file" in kwargs
:
186 filecontent
= kwargs
.pop("descriptor_file")
188 if not filecontent
.file:
190 "empty file or content", HTTPStatus
.BAD_REQUEST
193 indata
= filecontent
.file # .read()
195 if filecontent
.content_type
.value
:
196 cherrypy
.request
.headers
[
198 ] = filecontent
.content_type
.value
200 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
201 # "Only 'Content-Type' of type 'application/json' or
202 # 'application/yaml' for input format are available")
203 error_text
= "Invalid yaml format "
205 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
207 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
209 error_text
= "Invalid yaml format "
210 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
211 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
217 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
220 for k
, v
in kwargs
.items():
221 if isinstance(v
, str):
226 kwargs
[k
] = yaml
.load(v
, Loader
=yaml
.SafeLoader
)
232 or k
.endswith(".gte")
233 or k
.endswith(".lte")
242 elif v
.find(",") > 0:
243 kwargs
[k
] = v
.split(",")
244 elif isinstance(v
, (list, tuple)):
245 for index
in range(0, len(v
)):
250 v
[index
] = yaml
.load(v
[index
], Loader
=yaml
.SafeLoader
)
255 except (ValueError, yaml
.YAMLError
) as exc
:
256 raise RoException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
257 except KeyError as exc
:
258 raise RoException("Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
)
259 except Exception as exc
:
260 raise RoException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
263 def _format_out(data
, token_info
=None, _format
=None):
265 return string of dictionary data according to requested json, yaml, xml. By default json
266 :param data: response to be sent. Can be a dict, text or file
267 :param token_info: Contains among other username and project
268 :param _format: The format to be set as Content-Type if data is a file
271 accept
= cherrypy
.request
.headers
.get("Accept")
274 if accept
and "text/html" in accept
:
276 data
, cherrypy
.request
, cherrypy
.response
, token_info
279 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
281 elif hasattr(data
, "read"): # file object
283 cherrypy
.response
.headers
["Content-Type"] = _format
284 elif "b" in data
.mode
: # binariy asssumig zip
285 cherrypy
.response
.headers
["Content-Type"] = "application/zip"
287 cherrypy
.response
.headers
["Content-Type"] = "text/plain"
289 # TODO check that cherrypy close file. If not implement pending things to close per thread next
293 if "application/json" in accept
:
294 cherrypy
.response
.headers
[
296 ] = "application/json; charset=utf-8"
297 a
= json
.dumps(data
, indent
=4) + "\n"
299 return a
.encode("utf8")
300 elif "text/html" in accept
:
302 data
, cherrypy
.request
, cherrypy
.response
, token_info
305 "application/yaml" in accept
307 or "text/plain" in accept
310 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
311 elif cherrypy
.response
.status
>= 400:
312 raise cherrypy
.HTTPError(
313 HTTPStatus
.NOT_ACCEPTABLE
.value
,
314 "Only 'Accept' of type 'application/json' or 'application/yaml' "
315 "for output format are available",
318 cherrypy
.response
.headers
["Content-Type"] = "application/yaml"
320 return yaml
.safe_dump(
324 default_flow_style
=False,
328 ) # , canonical=True, default_style='"'
331 def index(self
, *args
, **kwargs
):
335 if cherrypy
.request
.method
== "GET":
336 token_info
= self
.authenticator
.authorize()
337 outdata
= token_info
# Home page
339 raise cherrypy
.HTTPError(
340 HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
341 "Method {} not allowed for tokens".format(cherrypy
.request
.method
),
344 return self
._format
_out
(outdata
, token_info
)
345 except (NsException
, AuthException
) as e
:
346 # cherrypy.log("index Exception {}".format(e))
347 cherrypy
.response
.status
= e
.http_code
.value
349 return self
._format
_out
("Welcome to OSM!", token_info
)
352 def version(self
, *args
, **kwargs
):
353 # TODO consider to remove and provide version using the static version file
355 if cherrypy
.request
.method
!= "GET":
357 "Only method GET is allowed",
358 HTTPStatus
.METHOD_NOT_ALLOWED
,
362 "Invalid URL or query string for version",
363 HTTPStatus
.METHOD_NOT_ALLOWED
,
366 # TODO include version of other modules, pick up from some kafka admin message
367 osm_ng_ro_version
= {"version": ro_version
, "date": ro_version_date
}
369 return self
._format
_out
(osm_ng_ro_version
)
370 except RoException
as e
:
371 cherrypy
.response
.status
= e
.http_code
.value
373 "code": e
.http_code
.name
,
374 "status": e
.http_code
.value
,
378 return self
._format
_out
(problem_details
, None)
380 def new_token(self
, engine_session
, indata
, *args
, **kwargs
):
384 token_info
= self
.authenticator
.authorize()
389 indata
.update(kwargs
)
391 # This is needed to log the user when authentication fails
392 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
393 token_info
= self
.authenticator
.new_token(
394 token_info
, indata
, cherrypy
.request
.remote
396 cherrypy
.session
["Authorization"] = token_info
["id"]
397 self
._set
_location
_header
("admin", "v1", "tokens", token_info
["id"])
400 # cherrypy.response.cookie["Authorization"] = outdata["id"]
401 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
403 return token_info
, token_info
["id"], True
405 def del_token(self
, engine_session
, indata
, version
, _id
, *args
, **kwargs
):
408 if not token_id
and "id" in kwargs
:
409 token_id
= kwargs
["id"]
411 token_info
= self
.authenticator
.authorize()
413 token_id
= token_info
["id"]
415 self
.authenticator
.del_token(token_id
)
417 cherrypy
.session
["Authorization"] = "logout"
418 # cherrypy.response.cookie["Authorization"] = token_id
419 # cherrypy.response.cookie["Authorization"]['expires'] = 0
421 return None, None, True
424 def test(self
, *args
, **kwargs
):
425 if not cherrypy
.config
.get("server.enable_test") or (
426 isinstance(cherrypy
.config
["server.enable_test"], str)
427 and cherrypy
.config
["server.enable_test"].lower() == "false"
429 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
431 return "test URL is disabled"
435 if args
and args
[0] == "help":
437 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
438 "sleep/<time>\nmessage/topic\n</pre></html>"
440 elif args
and args
[0] == "init":
442 # self.ns.load_dbase(cherrypy.request.app.config)
443 self
.ns
.create_admin()
445 return "Done. User 'admin', password 'admin' created"
447 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
449 return self
._format
_out
("Database already initialized")
450 elif args
and args
[0] == "file":
451 return cherrypy
.lib
.static
.serve_file(
452 cherrypy
.tree
.apps
["/ro"].config
["storage"]["path"] + "/" + args
[1],
456 elif args
and args
[0] == "file2":
457 f_path
= cherrypy
.tree
.apps
["/ro"].config
["storage"]["path"] + "/" + args
[1]
458 f
= open(f_path
, "r")
459 cherrypy
.response
.headers
["Content-type"] = "text/plain"
462 elif len(args
) == 2 and args
[0] == "db-clear":
463 deleted_info
= self
.ns
.db
.del_list(args
[1], kwargs
)
464 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
465 elif len(args
) and args
[0] == "fs-clear":
469 folders
= self
.ns
.fs
.dir_ls(".")
471 for folder
in folders
:
472 self
.ns
.fs
.file_delete(folder
)
474 return ",".join(folders
) + " folders deleted\n"
475 elif args
and args
[0] == "login":
476 if not cherrypy
.request
.headers
.get("Authorization"):
477 cherrypy
.response
.headers
[
479 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
480 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
481 elif args
and args
[0] == "login2":
482 if not cherrypy
.request
.headers
.get("Authorization"):
483 cherrypy
.response
.headers
[
485 ] = 'Bearer realm="Access to OSM site"'
486 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
487 elif args
and args
[0] == "sleep":
491 sleep_time
= int(args
[1])
493 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
494 return self
._format
_out
("Database already initialized")
496 thread_info
= cherrypy
.thread_data
498 time
.sleep(sleep_time
)
500 elif len(args
) >= 2 and args
[0] == "message":
502 return_text
= "<html><pre>{} ->\n".format(main_topic
)
505 if cherrypy
.request
.method
== "POST":
506 to_send
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
507 for k
, v
in to_send
.items():
508 self
.ns
.msg
.write(main_topic
, k
, v
)
509 return_text
+= " {}: {}\n".format(k
, v
)
510 elif cherrypy
.request
.method
== "GET":
511 for k
, v
in kwargs
.items():
513 main_topic
, k
, yaml
.load(v
, Loader
=yaml
.SafeLoader
)
515 return_text
+= " {}: {}\n".format(
516 k
, yaml
.load(v
, Loader
=yaml
.SafeLoader
)
518 except Exception as e
:
519 return_text
+= "Error: " + str(e
)
521 return_text
+= "</pre></html>\n"
526 "<html><pre>\nheaders:\n args: {}\n".format(args
)
527 + " kwargs: {}\n".format(kwargs
)
528 + " headers: {}\n".format(cherrypy
.request
.headers
)
529 + " path_info: {}\n".format(cherrypy
.request
.path_info
)
530 + " query_string: {}\n".format(cherrypy
.request
.query_string
)
531 + " session: {}\n".format(cherrypy
.session
)
532 + " cookie: {}\n".format(cherrypy
.request
.cookie
)
533 + " method: {}\n".format(cherrypy
.request
.method
)
534 + " session: {}\n".format(cherrypy
.session
.get("fieldname"))
537 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
539 if cherrypy
.request
.body
.length
:
540 return_text
+= " content: {}\n".format(
542 cherrypy
.request
.body
.read(
543 int(cherrypy
.request
.headers
.get("Content-Length", 0))
549 return_text
+= "thread: {}\n".format(thread_info
)
551 return_text
+= "</pre></html>"
556 def _check_valid_url_method(method
, *args
):
559 "URL must contain at least 'main_topic/version/topic'",
560 HTTPStatus
.METHOD_NOT_ALLOWED
,
563 reference
= valid_url_methods
568 if not isinstance(reference
, dict):
570 "URL contains unexpected extra items '{}'".format(arg
),
571 HTTPStatus
.METHOD_NOT_ALLOWED
,
575 reference
= reference
[arg
]
576 elif "<ID>" in reference
:
577 reference
= reference
["<ID>"]
578 elif "*" in reference
:
579 # reference = reference["*"]
583 "Unexpected URL item {}".format(arg
),
584 HTTPStatus
.METHOD_NOT_ALLOWED
,
587 if "TODO" in reference
and method
in reference
["TODO"]:
589 "Method {} not supported yet for this URL".format(method
),
590 HTTPStatus
.NOT_IMPLEMENTED
,
592 elif "METHODS" not in reference
or method
not in reference
["METHODS"]:
594 "Method {} not supported for this URL".format(method
),
595 HTTPStatus
.METHOD_NOT_ALLOWED
,
598 return reference
["ROLE_PERMISSION"] + method
.lower()
601 def _set_location_header(main_topic
, version
, topic
, id):
603 Insert response header Location with the URL of created item base on URL params
610 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
611 cherrypy
.response
.headers
["Location"] = "/ro/{}/{}/{}/{}".format(
612 main_topic
, version
, topic
, id
633 engine_session
= None
636 if not main_topic
or not version
or not topic
:
638 "URL must contain at least 'main_topic/version/topic'",
639 HTTPStatus
.METHOD_NOT_ALLOWED
,
642 if main_topic
not in (
647 "URL main_topic '{}' not supported".format(main_topic
),
648 HTTPStatus
.METHOD_NOT_ALLOWED
,
653 "URL version '{}' not supported".format(version
),
654 HTTPStatus
.METHOD_NOT_ALLOWED
,
659 and "METHOD" in kwargs
660 and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
662 method
= kwargs
.pop("METHOD")
664 method
= cherrypy
.request
.method
666 role_permission
= self
._check
_valid
_url
_method
(
667 method
, main_topic
, version
, topic
, _id
, _id2
, *args
, **kwargs
669 # skip token validation if requesting a token
670 indata
= self
._format
_in
(kwargs
)
672 if main_topic
!= "admin" or topic
!= "tokens":
673 token_info
= self
.authenticator
.authorize(role_permission
, _id
)
675 outdata
, created_id
, done
= self
.map_operation
[role_permission
](
676 engine_session
, indata
, version
, _id
, _id2
, *args
, *kwargs
680 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
682 cherrypy
.response
.status
= (
683 HTTPStatus
.ACCEPTED
.value
685 else HTTPStatus
.OK
.value
686 if outdata
is not None
687 else HTTPStatus
.NO_CONTENT
.value
690 return self
._format
_out
(outdata
, token_info
, _format
)
691 except Exception as e
:
704 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
705 http_code_name
= e
.http_code
.name
706 cherrypy
.log("Exception {}".format(e
))
709 cherrypy
.response
.status
710 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
711 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
712 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
714 if hasattr(outdata
, "close"): # is an open file
720 for rollback_item
in rollback
:
722 if rollback_item
.get("operation") == "set":
724 rollback_item
["topic"],
725 {"_id": rollback_item
["_id"]},
726 rollback_item
["content"],
731 rollback_item
["topic"],
732 {"_id": rollback_item
["_id"]},
735 except Exception as e2
:
736 rollback_error_text
= "Rollback Exception {}: {}".format(
739 cherrypy
.log(rollback_error_text
)
740 error_text
+= ". " + rollback_error_text
742 # if isinstance(e, MsgException):
743 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
744 # engine_topic[:-1], method, error_text)
746 "code": http_code_name
,
747 "status": http_code_value
,
748 "detail": error_text
,
751 return self
._format
_out
(problem_details
, token_info
)
752 # raise cherrypy.HTTPError(e.http_code.value, str(e))
755 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
756 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
757 if outdata
.get(logging_id
):
758 cherrypy
.request
.login
+= ";{}={}".format(
759 logging_id
, outdata
[logging_id
][:36]
763 def _start_service():
765 Callback function called when cherrypy.engine starts
766 Override configuration with env variables
767 Set database, storage, message configuration
768 Init database with admin/admin user password
770 global ro_server
, vim_admin_thread
772 cherrypy
.log
.error("Starting osm_ng_ro")
773 # update general cherrypy configuration
775 engine_config
= cherrypy
.tree
.apps
["/ro"].config
777 for k
, v
in environ
.items():
778 if not k
.startswith("OSMRO_"):
781 k1
, _
, k2
= k
[6:].lower().partition("_")
787 if k1
in ("server", "test", "auth", "log"):
788 # update [global] configuration
789 update_dict
[k1
+ "." + k2
] = yaml
.safe_load(v
)
791 # update [/static] configuration
792 engine_config
["/static"]["tools.staticdir." + k2
] = yaml
.safe_load(v
)
794 # update [/] configuration
795 engine_config
["/"]["tools." + k2
.replace("_", ".")] = yaml
.safe_load(v
)
796 elif k1
in ("message", "database", "storage", "authentication"):
797 engine_config
[k1
][k2
] = yaml
.safe_load(v
)
799 except Exception as e
:
800 raise RoException("Cannot load env '{}': {}".format(k
, e
))
803 cherrypy
.config
.update(update_dict
)
804 engine_config
["global"].update(update_dict
)
807 log_format_simple
= (
808 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
810 log_formatter_simple
= logging
.Formatter(
811 log_format_simple
, datefmt
="%Y-%m-%dT%H:%M:%S"
813 logger_server
= logging
.getLogger("cherrypy.error")
814 logger_access
= logging
.getLogger("cherrypy.access")
815 logger_cherry
= logging
.getLogger("cherrypy")
816 logger
= logging
.getLogger("ro")
818 if "log.file" in engine_config
["global"]:
819 file_handler
= logging
.handlers
.RotatingFileHandler(
820 engine_config
["global"]["log.file"], maxBytes
=100e6
, backupCount
=9, delay
=0
822 file_handler
.setFormatter(log_formatter_simple
)
823 logger_cherry
.addHandler(file_handler
)
824 logger
.addHandler(file_handler
)
826 # log always to standard output
827 for format_
, logger
in {
828 "ro.server %(filename)s:%(lineno)s": logger_server
,
829 "ro.access %(filename)s:%(lineno)s": logger_access
,
830 "%(name)s %(filename)s:%(lineno)s": logger
,
832 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
833 log_formatter_cherry
= logging
.Formatter(
834 log_format_cherry
, datefmt
="%Y-%m-%dT%H:%M:%S"
836 str_handler
= logging
.StreamHandler()
837 str_handler
.setFormatter(log_formatter_cherry
)
838 logger
.addHandler(str_handler
)
840 if engine_config
["global"].get("log.level"):
841 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
842 logger
.setLevel(engine_config
["global"]["log.level"])
844 # logging other modules
850 engine_config
[k1
]["logger_name"] = logname
851 logger_module
= logging
.getLogger(logname
)
853 if "logfile" in engine_config
[k1
]:
854 file_handler
= logging
.handlers
.RotatingFileHandler(
855 engine_config
[k1
]["logfile"], maxBytes
=100e6
, backupCount
=9, delay
=0
857 file_handler
.setFormatter(log_formatter_simple
)
858 logger_module
.addHandler(file_handler
)
860 if "loglevel" in engine_config
[k1
]:
861 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
862 # TODO add more entries, e.g.: storage
864 engine_config
["assignment"] = {}
865 # ^ each VIM, SDNc will be assigned one worker id. Ns class will add items and VimThread will auto-assign
866 cherrypy
.tree
.apps
["/ro"].root
.ns
.start(engine_config
)
867 cherrypy
.tree
.apps
["/ro"].root
.authenticator
.start(engine_config
)
868 cherrypy
.tree
.apps
["/ro"].root
.ns
.init_db(target_version
=database_version
)
870 # # start subscriptions thread:
871 vim_admin_thread
= VimAdminThread(config
=engine_config
, engine
=ro_server
.ns
)
872 vim_admin_thread
.start()
873 # # Do not capture except SubscriptionException
875 # backend = engine_config["authentication"]["backend"]
876 # cherrypy.log.error("Starting OSM NBI Version '{} {}' with '{}' authentication backend"
877 # .format(ro_version, ro_version_date, backend))
882 Callback function called when cherrypy.engine stops
883 TODO: Ending database connections.
885 global vim_admin_thread
887 # terminate vim_admin_thread
889 vim_admin_thread
.terminate()
891 vim_admin_thread
= None
892 cherrypy
.tree
.apps
["/ro"].root
.ns
.stop()
893 cherrypy
.log
.error("Stopping osm_ng_ro")
896 def ro_main(config_file
):
900 cherrypy
.engine
.subscribe("start", _start_service
)
901 cherrypy
.engine
.subscribe("stop", _stop_service
)
902 cherrypy
.quickstart(ro_server
, "/ro", config_file
)
907 """Usage: {} [options]
908 -c|--config [configuration_file]: loads the configuration file (default: ./ro.cfg)
909 -h|--help: shows this help
914 # --log-socket-host HOST: send logs to this host")
915 # --log-socket-port PORT: send logs using this port (default: 9022)")
918 if __name__
== "__main__":
920 # load parameters and configuration
921 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
922 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
926 if o
in ("-h", "--help"):
929 elif o
in ("-c", "--config"):
932 assert False, "Unhandled option"
935 if not path
.isfile(config_file
):
937 "configuration file '{}' that not exist".format(config_file
),
943 path
.dirname(__file__
) + "/ro.cfg",
947 if path
.isfile(config_file
):
951 "No configuration file 'ro.cfg' found neither at local folder nor at /etc/osm/",
957 except KeyboardInterrupt:
958 print("KeyboardInterrupt. Finishing", file=sys
.stderr
)
959 except getopt
.GetoptError
as e
:
960 print(str(e
), file=sys
.stderr
)