blob: 60f8e42498fd33f18d073f3877d1fb154507da86 [file] [log] [blame]
tierno1d213f42020-04-24 14:02:51 +00001#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3
4##
5# Copyright 2020 Telefonica Investigacion y Desarrollo, S.A.U.
6#
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
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
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
16# implied.
17# See the License for the specific language governing permissions and
18# limitations under the License.
19##
20
sousaedu049cbb12022-01-05 11:39:35 +000021
22from codecs import getreader
23import getopt
24from http import HTTPStatus
tierno1d213f42020-04-24 14:02:51 +000025import json
tierno1d213f42020-04-24 14:02:51 +000026import logging
27import logging.handlers
sousaedu049cbb12022-01-05 11:39:35 +000028from os import environ, path
tierno1d213f42020-04-24 14:02:51 +000029import sys
sousaedu049cbb12022-01-05 11:39:35 +000030import time
tierno1d213f42020-04-24 14:02:51 +000031
sousaedu049cbb12022-01-05 11:39:35 +000032import cherrypy
tierno1d213f42020-04-24 14:02:51 +000033from osm_common.dbbase import DbException
34from osm_common.fsbase import FsException
35from osm_common.msgbase import MsgException
tierno1d213f42020-04-24 14:02:51 +000036from osm_ng_ro import version as ro_version, version_date as ro_version_date
sousaedu049cbb12022-01-05 11:39:35 +000037import osm_ng_ro.html_out as html
Gulsum Aticid586d892023-02-13 18:40:03 +030038from osm_ng_ro.monitor import start_monitoring, stop_monitoring
sousaedu049cbb12022-01-05 11:39:35 +000039from osm_ng_ro.ns import Ns, NsException
40from osm_ng_ro.validation import ValidationError
41from osm_ng_ro.vim_admin import VimAdminThread
42import yaml
43
tierno1d213f42020-04-24 14:02:51 +000044
45__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
sousaedu80135b92021-02-17 15:05:18 +010046__version__ = "0.1." # file version, not NBI version
tierno1d213f42020-04-24 14:02:51 +000047version_date = "May 2020"
48
sousaedu80135b92021-02-17 15:05:18 +010049database_version = "1.2"
50auth_database_version = "1.0"
51ro_server = None # instance of Server class
52vim_admin_thread = None # instance of VimAdminThread class
tierno70eeb182020-10-19 16:38:00 +000053
tierno1d213f42020-04-24 14:02:51 +000054# vim_threads = None # instance of VimThread class
55
56"""
57RO North Bound Interface
58URL: /ro GET POST PUT DELETE PATCH
59 /ns/v1/deploy O
60 /<nsrs_id> O O O
61 /<action_id> O
62 /cancel O
tierno1d213f42020-04-24 14:02:51 +000063"""
64
65valid_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
72
73valid_url_methods = {
74 # contains allowed URL and methods, and the role_permission name
75 "admin": {
76 "v1": {
77 "tokens": {
78 "METHODS": ("POST",),
79 "ROLE_PERMISSION": "tokens:",
sousaedu80135b92021-02-17 15:05:18 +010080 "<ID>": {"METHODS": ("DELETE",), "ROLE_PERMISSION": "tokens:id:"},
tierno1d213f42020-04-24 14:02:51 +000081 },
82 }
83 },
84 "ns": {
85 "v1": {
k4.rahul78f474e2022-05-02 15:47:57 +000086 "rebuild": {
87 "METHODS": ("POST",),
88 "ROLE_PERMISSION": "rebuild:",
89 "<ID>": {
90 "METHODS": ("POST",),
91 "ROLE_PERMISSION": "rebuild:id:",
92 },
93 },
94 "start": {
95 "METHODS": ("POST",),
96 "ROLE_PERMISSION": "start:",
97 "<ID>": {
98 "METHODS": ("POST",),
99 "ROLE_PERMISSION": "start:id:",
100 },
101 },
102 "stop": {
103 "METHODS": ("POST",),
104 "ROLE_PERMISSION": "stop:",
105 "<ID>": {
106 "METHODS": ("POST",),
107 "ROLE_PERMISSION": "stop:id:",
108 },
109 },
Isabel Lloretd555b292025-04-28 09:00:08 +0000110 "console": {
111 "METHODS": ("POST",),
112 "ROLE_PERMISSION": "console:",
113 "<ID>": {
114 "METHODS": ("POST",),
115 "ROLE_PERMISSION": "console:id:",
116 },
117 },
tierno1d213f42020-04-24 14:02:51 +0000118 "deploy": {
119 "METHODS": ("GET",),
120 "ROLE_PERMISSION": "deploy:",
121 "<ID>": {
122 "METHODS": ("GET", "POST", "DELETE"),
123 "ROLE_PERMISSION": "deploy:id:",
124 "<ID>": {
125 "METHODS": ("GET",),
126 "ROLE_PERMISSION": "deploy:id:id:",
127 "cancel": {
128 "METHODS": ("POST",),
129 "ROLE_PERMISSION": "deploy:id:id:cancel",
sousaedu80135b92021-02-17 15:05:18 +0100130 },
Isabel Lloretd555b292025-04-28 09:00:08 +0000131 "viminfo": {
132 "METHODS": ("GET",),
133 "ROLE_PERMISSION": "deploy:id:id:viminfo:",
134 },
sousaedu80135b92021-02-17 15:05:18 +0100135 },
136 },
tierno1d213f42020-04-24 14:02:51 +0000137 },
palaciosj8f2060b2022-02-24 12:05:59 +0000138 "recreate": {
139 "<ID>": {
140 "METHODS": ("POST"),
141 "ROLE_PERMISSION": "recreate:id:",
142 "<ID>": {
143 "METHODS": ("GET",),
144 "ROLE_PERMISSION": "recreate:id:id:",
145 },
146 },
147 },
elumalai8658c2c2022-04-28 19:09:31 +0530148 "migrate": {
149 "<ID>": {
150 "METHODS": ("POST"),
151 "ROLE_PERMISSION": "migrate:id:",
152 "<ID>": {
153 "METHODS": ("GET",),
154 "ROLE_PERMISSION": "migrate:id:id:",
155 },
156 },
157 },
sritharan29a4c1a2022-05-05 12:15:04 +0000158 "verticalscale": {
159 "<ID>": {
160 "METHODS": ("POST"),
161 "ROLE_PERMISSION": "verticalscale:id:",
162 "<ID>": {
163 "METHODS": ("GET",),
164 "ROLE_PERMISSION": "verticalscale:id:id:",
165 },
166 },
167 },
tierno1d213f42020-04-24 14:02:51 +0000168 }
169 },
170}
171
172
173class RoException(Exception):
tierno1d213f42020-04-24 14:02:51 +0000174 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
175 Exception.__init__(self, message)
176 self.http_code = http_code
177
178
179class AuthException(RoException):
180 pass
181
182
183class Authenticator:
tierno1d213f42020-04-24 14:02:51 +0000184 def __init__(self, valid_url_methods, valid_query_string):
185 self.valid_url_methods = valid_url_methods
186 self.valid_query_string = valid_query_string
187
188 def authorize(self, *args, **kwargs):
189 return {"token": "ok", "id": "ok"}
sousaedu80135b92021-02-17 15:05:18 +0100190
tierno1d213f42020-04-24 14:02:51 +0000191 def new_token(self, token_info, indata, remote):
sousaedu80135b92021-02-17 15:05:18 +0100192 return {"token": "ok", "id": "ok", "remote": remote}
tierno1d213f42020-04-24 14:02:51 +0000193
194 def del_token(self, token_id):
195 pass
196
197 def start(self, engine_config):
198 pass
199
200
201class Server(object):
202 instance = 0
203 # to decode bytes to str
204 reader = getreader("utf-8")
205
206 def __init__(self):
207 self.instance += 1
208 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
209 self.ns = Ns()
210 self.map_operation = {
211 "token:post": self.new_token,
212 "token:id:delete": self.del_token,
213 "deploy:get": self.ns.get_deploy,
214 "deploy:id:get": self.ns.get_actions,
215 "deploy:id:post": self.ns.deploy,
216 "deploy:id:delete": self.ns.delete,
217 "deploy:id:id:get": self.ns.status,
Isabel Lloretd555b292025-04-28 09:00:08 +0000218 "deploy:id:id:viminfo:get": self.ns.get_action_viminfo,
tierno1d213f42020-04-24 14:02:51 +0000219 "deploy:id:id:cancel:post": self.ns.cancel,
k4.rahul78f474e2022-05-02 15:47:57 +0000220 "rebuild:id:post": self.ns.rebuild_start_stop,
221 "start:id:post": self.ns.rebuild_start_stop,
222 "stop:id:post": self.ns.rebuild_start_stop,
Isabel Lloretd555b292025-04-28 09:00:08 +0000223 "console:id:post": self.ns.prepare_get_console,
palaciosj8f2060b2022-02-24 12:05:59 +0000224 "recreate:id:post": self.ns.recreate,
225 "recreate:id:id:get": self.ns.recreate_status,
elumalai8658c2c2022-04-28 19:09:31 +0530226 "migrate:id:post": self.ns.migrate,
sritharan29a4c1a2022-05-05 12:15:04 +0000227 "verticalscale:id:post": self.ns.verticalscale,
tierno1d213f42020-04-24 14:02:51 +0000228 }
229
230 def _format_in(self, kwargs):
Gulsum Atici2f995052022-11-23 16:06:40 +0300231 error_text = ""
tierno1d213f42020-04-24 14:02:51 +0000232 try:
233 indata = None
sousaedu80135b92021-02-17 15:05:18 +0100234
tierno1d213f42020-04-24 14:02:51 +0000235 if cherrypy.request.body.length:
236 error_text = "Invalid input format "
237
238 if "Content-Type" in cherrypy.request.headers:
239 if "application/json" in cherrypy.request.headers["Content-Type"]:
240 error_text = "Invalid json format "
241 indata = json.load(self.reader(cherrypy.request.body))
242 cherrypy.request.headers.pop("Content-File-MD5", None)
243 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
244 error_text = "Invalid yaml format "
aticig7b521f72022-07-15 00:43:09 +0300245 indata = yaml.safe_load(cherrypy.request.body)
tierno1d213f42020-04-24 14:02:51 +0000246 cherrypy.request.headers.pop("Content-File-MD5", None)
sousaedu80135b92021-02-17 15:05:18 +0100247 elif (
248 "application/binary" in cherrypy.request.headers["Content-Type"]
249 or "application/gzip"
250 in cherrypy.request.headers["Content-Type"]
251 or "application/zip" in cherrypy.request.headers["Content-Type"]
252 or "text/plain" in cherrypy.request.headers["Content-Type"]
253 ):
tierno1d213f42020-04-24 14:02:51 +0000254 indata = cherrypy.request.body # .read()
sousaedu80135b92021-02-17 15:05:18 +0100255 elif (
256 "multipart/form-data"
257 in cherrypy.request.headers["Content-Type"]
258 ):
tierno1d213f42020-04-24 14:02:51 +0000259 if "descriptor_file" in kwargs:
260 filecontent = kwargs.pop("descriptor_file")
sousaedu80135b92021-02-17 15:05:18 +0100261
tierno1d213f42020-04-24 14:02:51 +0000262 if not filecontent.file:
sousaedu80135b92021-02-17 15:05:18 +0100263 raise RoException(
264 "empty file or content", HTTPStatus.BAD_REQUEST
265 )
266
tierno1d213f42020-04-24 14:02:51 +0000267 indata = filecontent.file # .read()
sousaedu80135b92021-02-17 15:05:18 +0100268
tierno1d213f42020-04-24 14:02:51 +0000269 if filecontent.content_type.value:
garciadeblasaca8cb52023-12-21 16:28:15 +0100270 cherrypy.request.headers["Content-Type"] = (
271 filecontent.content_type.value
272 )
tierno1d213f42020-04-24 14:02:51 +0000273 else:
274 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
275 # "Only 'Content-Type' of type 'application/json' or
276 # 'application/yaml' for input format are available")
277 error_text = "Invalid yaml format "
aticig7b521f72022-07-15 00:43:09 +0300278 indata = yaml.safe_load(cherrypy.request.body)
tierno1d213f42020-04-24 14:02:51 +0000279 cherrypy.request.headers.pop("Content-File-MD5", None)
280 else:
281 error_text = "Invalid yaml format "
aticig7b521f72022-07-15 00:43:09 +0300282 indata = yaml.safe_load(cherrypy.request.body)
tierno1d213f42020-04-24 14:02:51 +0000283 cherrypy.request.headers.pop("Content-File-MD5", None)
sousaedu80135b92021-02-17 15:05:18 +0100284
tierno1d213f42020-04-24 14:02:51 +0000285 if not indata:
286 indata = {}
287
288 format_yaml = False
289 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
290 format_yaml = True
291
292 for k, v in kwargs.items():
293 if isinstance(v, str):
294 if v == "":
295 kwargs[k] = None
296 elif format_yaml:
297 try:
aticig7b521f72022-07-15 00:43:09 +0300298 kwargs[k] = yaml.safe_load(v)
299 except Exception as yaml_error:
300 logging.exception(
301 f"{yaml_error} occured while parsing the yaml"
302 )
sousaedu80135b92021-02-17 15:05:18 +0100303 elif (
304 k.endswith(".gt")
305 or k.endswith(".lt")
306 or k.endswith(".gte")
307 or k.endswith(".lte")
308 ):
tierno1d213f42020-04-24 14:02:51 +0000309 try:
310 kwargs[k] = int(v)
311 except Exception:
312 try:
313 kwargs[k] = float(v)
aticig7b521f72022-07-15 00:43:09 +0300314 except Exception as keyword_error:
315 logging.exception(
316 f"{keyword_error} occured while getting the keyword arguments"
317 )
tierno1d213f42020-04-24 14:02:51 +0000318 elif v.find(",") > 0:
319 kwargs[k] = v.split(",")
320 elif isinstance(v, (list, tuple)):
321 for index in range(0, len(v)):
322 if v[index] == "":
323 v[index] = None
324 elif format_yaml:
325 try:
aticig7b521f72022-07-15 00:43:09 +0300326 v[index] = yaml.safe_load(v[index])
327 except Exception as error:
328 logging.exception(
329 f"{error} occured while parsing the yaml"
330 )
tierno1d213f42020-04-24 14:02:51 +0000331
332 return indata
333 except (ValueError, yaml.YAMLError) as exc:
334 raise RoException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
335 except KeyError as exc:
336 raise RoException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
337 except Exception as exc:
338 raise RoException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
339
340 @staticmethod
341 def _format_out(data, token_info=None, _format=None):
342 """
343 return string of dictionary data according to requested json, yaml, xml. By default json
344 :param data: response to be sent. Can be a dict, text or file
345 :param token_info: Contains among other username and project
346 :param _format: The format to be set as Content-Type if data is a file
347 :return: None
348 """
349 accept = cherrypy.request.headers.get("Accept")
sousaedu80135b92021-02-17 15:05:18 +0100350
tierno1d213f42020-04-24 14:02:51 +0000351 if data is None:
352 if accept and "text/html" in accept:
sousaedu80135b92021-02-17 15:05:18 +0100353 return html.format(
354 data, cherrypy.request, cherrypy.response, token_info
355 )
356
tierno1d213f42020-04-24 14:02:51 +0000357 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
358 return
359 elif hasattr(data, "read"): # file object
360 if _format:
361 cherrypy.response.headers["Content-Type"] = _format
362 elif "b" in data.mode: # binariy asssumig zip
sousaedu80135b92021-02-17 15:05:18 +0100363 cherrypy.response.headers["Content-Type"] = "application/zip"
tierno1d213f42020-04-24 14:02:51 +0000364 else:
sousaedu80135b92021-02-17 15:05:18 +0100365 cherrypy.response.headers["Content-Type"] = "text/plain"
366
tierno1d213f42020-04-24 14:02:51 +0000367 # TODO check that cherrypy close file. If not implement pending things to close per thread next
368 return data
sousaedu80135b92021-02-17 15:05:18 +0100369
tierno1d213f42020-04-24 14:02:51 +0000370 if accept:
371 if "application/json" in accept:
garciadeblasaca8cb52023-12-21 16:28:15 +0100372 cherrypy.response.headers["Content-Type"] = (
373 "application/json; charset=utf-8"
374 )
tierno1d213f42020-04-24 14:02:51 +0000375 a = json.dumps(data, indent=4) + "\n"
sousaedu80135b92021-02-17 15:05:18 +0100376
tierno1d213f42020-04-24 14:02:51 +0000377 return a.encode("utf8")
378 elif "text/html" in accept:
sousaedu80135b92021-02-17 15:05:18 +0100379 return html.format(
380 data, cherrypy.request, cherrypy.response, token_info
381 )
382 elif (
383 "application/yaml" in accept
384 or "*/*" in accept
385 or "text/plain" in accept
386 ):
tierno1d213f42020-04-24 14:02:51 +0000387 pass
388 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
389 elif cherrypy.response.status >= 400:
sousaedu80135b92021-02-17 15:05:18 +0100390 raise cherrypy.HTTPError(
391 HTTPStatus.NOT_ACCEPTABLE.value,
392 "Only 'Accept' of type 'application/json' or 'application/yaml' "
393 "for output format are available",
394 )
395
396 cherrypy.response.headers["Content-Type"] = "application/yaml"
397
398 return yaml.safe_dump(
399 data,
400 explicit_start=True,
401 indent=4,
402 default_flow_style=False,
403 tags=False,
404 encoding="utf-8",
405 allow_unicode=True,
406 ) # , canonical=True, default_style='"'
tierno1d213f42020-04-24 14:02:51 +0000407
408 @cherrypy.expose
409 def index(self, *args, **kwargs):
410 token_info = None
sousaedu80135b92021-02-17 15:05:18 +0100411
tierno1d213f42020-04-24 14:02:51 +0000412 try:
413 if cherrypy.request.method == "GET":
414 token_info = self.authenticator.authorize()
sousaedu80135b92021-02-17 15:05:18 +0100415 outdata = token_info # Home page
tierno1d213f42020-04-24 14:02:51 +0000416 else:
sousaedu80135b92021-02-17 15:05:18 +0100417 raise cherrypy.HTTPError(
418 HTTPStatus.METHOD_NOT_ALLOWED.value,
419 "Method {} not allowed for tokens".format(cherrypy.request.method),
420 )
tierno1d213f42020-04-24 14:02:51 +0000421
422 return self._format_out(outdata, token_info)
tierno1d213f42020-04-24 14:02:51 +0000423 except (NsException, AuthException) as e:
424 # cherrypy.log("index Exception {}".format(e))
425 cherrypy.response.status = e.http_code.value
sousaedu80135b92021-02-17 15:05:18 +0100426
tierno1d213f42020-04-24 14:02:51 +0000427 return self._format_out("Welcome to OSM!", token_info)
428
429 @cherrypy.expose
430 def version(self, *args, **kwargs):
431 # TODO consider to remove and provide version using the static version file
432 try:
433 if cherrypy.request.method != "GET":
sousaedu80135b92021-02-17 15:05:18 +0100434 raise RoException(
435 "Only method GET is allowed",
436 HTTPStatus.METHOD_NOT_ALLOWED,
437 )
tierno1d213f42020-04-24 14:02:51 +0000438 elif args or kwargs:
sousaedu80135b92021-02-17 15:05:18 +0100439 raise RoException(
440 "Invalid URL or query string for version",
441 HTTPStatus.METHOD_NOT_ALLOWED,
442 )
443
tierno1d213f42020-04-24 14:02:51 +0000444 # TODO include version of other modules, pick up from some kafka admin message
445 osm_ng_ro_version = {"version": ro_version, "date": ro_version_date}
sousaedu80135b92021-02-17 15:05:18 +0100446
tierno1d213f42020-04-24 14:02:51 +0000447 return self._format_out(osm_ng_ro_version)
448 except RoException as e:
449 cherrypy.response.status = e.http_code.value
450 problem_details = {
451 "code": e.http_code.name,
452 "status": e.http_code.value,
453 "detail": str(e),
454 }
sousaedu80135b92021-02-17 15:05:18 +0100455
tierno1d213f42020-04-24 14:02:51 +0000456 return self._format_out(problem_details, None)
457
458 def new_token(self, engine_session, indata, *args, **kwargs):
459 token_info = None
460
461 try:
462 token_info = self.authenticator.authorize()
463 except Exception:
464 token_info = None
sousaedu80135b92021-02-17 15:05:18 +0100465
tierno1d213f42020-04-24 14:02:51 +0000466 if kwargs:
467 indata.update(kwargs)
sousaedu80135b92021-02-17 15:05:18 +0100468
tierno1d213f42020-04-24 14:02:51 +0000469 # This is needed to log the user when authentication fails
470 cherrypy.request.login = "{}".format(indata.get("username", "-"))
sousaedu80135b92021-02-17 15:05:18 +0100471 token_info = self.authenticator.new_token(
472 token_info, indata, cherrypy.request.remote
473 )
474 cherrypy.session["Authorization"] = token_info["id"]
tierno1d213f42020-04-24 14:02:51 +0000475 self._set_location_header("admin", "v1", "tokens", token_info["id"])
476 # for logging
477
478 # cherrypy.response.cookie["Authorization"] = outdata["id"]
479 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
sousaedu80135b92021-02-17 15:05:18 +0100480
tierno1d213f42020-04-24 14:02:51 +0000481 return token_info, token_info["id"], True
482
483 def del_token(self, engine_session, indata, version, _id, *args, **kwargs):
484 token_id = _id
sousaedu80135b92021-02-17 15:05:18 +0100485
tierno1d213f42020-04-24 14:02:51 +0000486 if not token_id and "id" in kwargs:
487 token_id = kwargs["id"]
488 elif not token_id:
489 token_info = self.authenticator.authorize()
490 # for logging
491 token_id = token_info["id"]
sousaedu80135b92021-02-17 15:05:18 +0100492
tierno1d213f42020-04-24 14:02:51 +0000493 self.authenticator.del_token(token_id)
494 token_info = None
sousaedu80135b92021-02-17 15:05:18 +0100495 cherrypy.session["Authorization"] = "logout"
tierno1d213f42020-04-24 14:02:51 +0000496 # cherrypy.response.cookie["Authorization"] = token_id
497 # cherrypy.response.cookie["Authorization"]['expires'] = 0
sousaedu80135b92021-02-17 15:05:18 +0100498
tierno1d213f42020-04-24 14:02:51 +0000499 return None, None, True
sousaedu80135b92021-02-17 15:05:18 +0100500
tierno1d213f42020-04-24 14:02:51 +0000501 @cherrypy.expose
502 def test(self, *args, **kwargs):
sousaedu80135b92021-02-17 15:05:18 +0100503 if not cherrypy.config.get("server.enable_test") or (
504 isinstance(cherrypy.config["server.enable_test"], str)
505 and cherrypy.config["server.enable_test"].lower() == "false"
506 ):
tierno1d213f42020-04-24 14:02:51 +0000507 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value
tierno1d213f42020-04-24 14:02:51 +0000508
sousaedu80135b92021-02-17 15:05:18 +0100509 return "test URL is disabled"
510
511 thread_info = None
512
513 if args and args[0] == "help":
514 return (
515 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
516 "sleep/<time>\nmessage/topic\n</pre></html>"
517 )
tierno1d213f42020-04-24 14:02:51 +0000518 elif args and args[0] == "init":
519 try:
520 # self.ns.load_dbase(cherrypy.request.app.config)
521 self.ns.create_admin()
sousaedu80135b92021-02-17 15:05:18 +0100522
tierno1d213f42020-04-24 14:02:51 +0000523 return "Done. User 'admin', password 'admin' created"
524 except Exception:
525 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
sousaedu80135b92021-02-17 15:05:18 +0100526
tierno1d213f42020-04-24 14:02:51 +0000527 return self._format_out("Database already initialized")
528 elif args and args[0] == "file":
sousaedu80135b92021-02-17 15:05:18 +0100529 return cherrypy.lib.static.serve_file(
530 cherrypy.tree.apps["/ro"].config["storage"]["path"] + "/" + args[1],
531 "text/plain",
532 "attachment",
533 )
tierno1d213f42020-04-24 14:02:51 +0000534 elif args and args[0] == "file2":
sousaedu80135b92021-02-17 15:05:18 +0100535 f_path = cherrypy.tree.apps["/ro"].config["storage"]["path"] + "/" + args[1]
tierno1d213f42020-04-24 14:02:51 +0000536 f = open(f_path, "r")
537 cherrypy.response.headers["Content-type"] = "text/plain"
538 return f
539
540 elif len(args) == 2 and args[0] == "db-clear":
541 deleted_info = self.ns.db.del_list(args[1], kwargs)
542 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
543 elif len(args) and args[0] == "fs-clear":
544 if len(args) >= 2:
545 folders = (args[1],)
546 else:
547 folders = self.ns.fs.dir_ls(".")
sousaedu80135b92021-02-17 15:05:18 +0100548
tierno1d213f42020-04-24 14:02:51 +0000549 for folder in folders:
550 self.ns.fs.file_delete(folder)
sousaedu80135b92021-02-17 15:05:18 +0100551
tierno1d213f42020-04-24 14:02:51 +0000552 return ",".join(folders) + " folders deleted\n"
553 elif args and args[0] == "login":
554 if not cherrypy.request.headers.get("Authorization"):
garciadeblasaca8cb52023-12-21 16:28:15 +0100555 cherrypy.response.headers["WWW-Authenticate"] = (
556 'Basic realm="Access to OSM site", charset="UTF-8"'
557 )
tierno1d213f42020-04-24 14:02:51 +0000558 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
559 elif args and args[0] == "login2":
560 if not cherrypy.request.headers.get("Authorization"):
garciadeblasaca8cb52023-12-21 16:28:15 +0100561 cherrypy.response.headers["WWW-Authenticate"] = (
562 'Bearer realm="Access to OSM site"'
563 )
tierno1d213f42020-04-24 14:02:51 +0000564 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
565 elif args and args[0] == "sleep":
566 sleep_time = 5
sousaedu80135b92021-02-17 15:05:18 +0100567
tierno1d213f42020-04-24 14:02:51 +0000568 try:
569 sleep_time = int(args[1])
570 except Exception:
571 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
572 return self._format_out("Database already initialized")
sousaedu80135b92021-02-17 15:05:18 +0100573
tierno1d213f42020-04-24 14:02:51 +0000574 thread_info = cherrypy.thread_data
575 print(thread_info)
576 time.sleep(sleep_time)
577 # thread_info
578 elif len(args) >= 2 and args[0] == "message":
579 main_topic = args[1]
580 return_text = "<html><pre>{} ->\n".format(main_topic)
sousaedu80135b92021-02-17 15:05:18 +0100581
tierno1d213f42020-04-24 14:02:51 +0000582 try:
sousaedu80135b92021-02-17 15:05:18 +0100583 if cherrypy.request.method == "POST":
aticig7b521f72022-07-15 00:43:09 +0300584 to_send = yaml.safe_load(cherrypy.request.body)
tierno1d213f42020-04-24 14:02:51 +0000585 for k, v in to_send.items():
586 self.ns.msg.write(main_topic, k, v)
587 return_text += " {}: {}\n".format(k, v)
sousaedu80135b92021-02-17 15:05:18 +0100588 elif cherrypy.request.method == "GET":
tierno1d213f42020-04-24 14:02:51 +0000589 for k, v in kwargs.items():
aticig7b521f72022-07-15 00:43:09 +0300590 self.ns.msg.write(main_topic, k, yaml.safe_load(v))
591 return_text += " {}: {}\n".format(k, yaml.safe_load(v))
tierno1d213f42020-04-24 14:02:51 +0000592 except Exception as e:
593 return_text += "Error: " + str(e)
sousaedu80135b92021-02-17 15:05:18 +0100594
tierno1d213f42020-04-24 14:02:51 +0000595 return_text += "</pre></html>\n"
sousaedu80135b92021-02-17 15:05:18 +0100596
tierno1d213f42020-04-24 14:02:51 +0000597 return return_text
598
599 return_text = (
sousaedu80135b92021-02-17 15:05:18 +0100600 "<html><pre>\nheaders:\n args: {}\n".format(args)
601 + " kwargs: {}\n".format(kwargs)
602 + " headers: {}\n".format(cherrypy.request.headers)
603 + " path_info: {}\n".format(cherrypy.request.path_info)
604 + " query_string: {}\n".format(cherrypy.request.query_string)
605 + " session: {}\n".format(cherrypy.session)
606 + " cookie: {}\n".format(cherrypy.request.cookie)
607 + " method: {}\n".format(cherrypy.request.method)
608 + " session: {}\n".format(cherrypy.session.get("fieldname"))
609 + " body:\n"
610 )
tierno1d213f42020-04-24 14:02:51 +0000611 return_text += " length: {}\n".format(cherrypy.request.body.length)
sousaedu80135b92021-02-17 15:05:18 +0100612
tierno1d213f42020-04-24 14:02:51 +0000613 if cherrypy.request.body.length:
614 return_text += " content: {}\n".format(
sousaedu80135b92021-02-17 15:05:18 +0100615 str(
616 cherrypy.request.body.read(
617 int(cherrypy.request.headers.get("Content-Length", 0))
618 )
619 )
620 )
621
tierno1d213f42020-04-24 14:02:51 +0000622 if thread_info:
623 return_text += "thread: {}\n".format(thread_info)
sousaedu80135b92021-02-17 15:05:18 +0100624
tierno1d213f42020-04-24 14:02:51 +0000625 return_text += "</pre></html>"
sousaedu80135b92021-02-17 15:05:18 +0100626
tierno1d213f42020-04-24 14:02:51 +0000627 return return_text
628
629 @staticmethod
630 def _check_valid_url_method(method, *args):
631 if len(args) < 3:
sousaedu80135b92021-02-17 15:05:18 +0100632 raise RoException(
633 "URL must contain at least 'main_topic/version/topic'",
634 HTTPStatus.METHOD_NOT_ALLOWED,
635 )
tierno1d213f42020-04-24 14:02:51 +0000636
637 reference = valid_url_methods
638 for arg in args:
639 if arg is None:
640 break
sousaedu80135b92021-02-17 15:05:18 +0100641
tierno1d213f42020-04-24 14:02:51 +0000642 if not isinstance(reference, dict):
sousaedu80135b92021-02-17 15:05:18 +0100643 raise RoException(
644 "URL contains unexpected extra items '{}'".format(arg),
645 HTTPStatus.METHOD_NOT_ALLOWED,
646 )
tierno1d213f42020-04-24 14:02:51 +0000647
648 if arg in reference:
649 reference = reference[arg]
650 elif "<ID>" in reference:
651 reference = reference["<ID>"]
652 elif "*" in reference:
653 # reference = reference["*"]
654 break
655 else:
sousaedu80135b92021-02-17 15:05:18 +0100656 raise RoException(
657 "Unexpected URL item {}".format(arg),
658 HTTPStatus.METHOD_NOT_ALLOWED,
659 )
660
tierno1d213f42020-04-24 14:02:51 +0000661 if "TODO" in reference and method in reference["TODO"]:
sousaedu80135b92021-02-17 15:05:18 +0100662 raise RoException(
663 "Method {} not supported yet for this URL".format(method),
664 HTTPStatus.NOT_IMPLEMENTED,
665 )
tierno1d213f42020-04-24 14:02:51 +0000666 elif "METHODS" not in reference or method not in reference["METHODS"]:
sousaedu80135b92021-02-17 15:05:18 +0100667 raise RoException(
668 "Method {} not supported for this URL".format(method),
669 HTTPStatus.METHOD_NOT_ALLOWED,
670 )
671
tierno1d213f42020-04-24 14:02:51 +0000672 return reference["ROLE_PERMISSION"] + method.lower()
673
674 @staticmethod
675 def _set_location_header(main_topic, version, topic, id):
676 """
677 Insert response header Location with the URL of created item base on URL params
678 :param main_topic:
679 :param version:
680 :param topic:
681 :param id:
682 :return: None
683 """
684 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
sousaedu80135b92021-02-17 15:05:18 +0100685 cherrypy.response.headers["Location"] = "/ro/{}/{}/{}/{}".format(
686 main_topic, version, topic, id
687 )
688
tierno1d213f42020-04-24 14:02:51 +0000689 return
690
691 @cherrypy.expose
sousaedu80135b92021-02-17 15:05:18 +0100692 def default(
693 self,
694 main_topic=None,
695 version=None,
696 topic=None,
697 _id=None,
698 _id2=None,
699 *args,
700 **kwargs,
701 ):
tierno1d213f42020-04-24 14:02:51 +0000702 token_info = None
elumalai321d2e92023-04-28 17:03:07 +0530703 outdata = {}
tierno1d213f42020-04-24 14:02:51 +0000704 _format = None
705 method = "DONE"
706 rollback = []
707 engine_session = None
sousaedu80135b92021-02-17 15:05:18 +0100708
tierno1d213f42020-04-24 14:02:51 +0000709 try:
710 if not main_topic or not version or not topic:
sousaedu80135b92021-02-17 15:05:18 +0100711 raise RoException(
712 "URL must contain at least 'main_topic/version/topic'",
713 HTTPStatus.METHOD_NOT_ALLOWED,
714 )
tierno1d213f42020-04-24 14:02:51 +0000715
sousaedu80135b92021-02-17 15:05:18 +0100716 if main_topic not in (
717 "admin",
718 "ns",
719 ):
720 raise RoException(
721 "URL main_topic '{}' not supported".format(main_topic),
722 HTTPStatus.METHOD_NOT_ALLOWED,
723 )
724
725 if version != "v1":
726 raise RoException(
727 "URL version '{}' not supported".format(version),
728 HTTPStatus.METHOD_NOT_ALLOWED,
729 )
730
731 if (
732 kwargs
733 and "METHOD" in kwargs
734 and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
735 ):
tierno1d213f42020-04-24 14:02:51 +0000736 method = kwargs.pop("METHOD")
737 else:
738 method = cherrypy.request.method
739
sousaedu80135b92021-02-17 15:05:18 +0100740 role_permission = self._check_valid_url_method(
741 method, main_topic, version, topic, _id, _id2, *args, **kwargs
742 )
tierno1d213f42020-04-24 14:02:51 +0000743 # skip token validation if requesting a token
744 indata = self._format_in(kwargs)
sousaedu80135b92021-02-17 15:05:18 +0100745
tierno1d213f42020-04-24 14:02:51 +0000746 if main_topic != "admin" or topic != "tokens":
747 token_info = self.authenticator.authorize(role_permission, _id)
sousaedu80135b92021-02-17 15:05:18 +0100748
tierno1d213f42020-04-24 14:02:51 +0000749 outdata, created_id, done = self.map_operation[role_permission](
sousaedu80135b92021-02-17 15:05:18 +0100750 engine_session, indata, version, _id, _id2, *args, *kwargs
751 )
752
tierno1d213f42020-04-24 14:02:51 +0000753 if created_id:
754 self._set_location_header(main_topic, version, topic, _id)
sousaedu80135b92021-02-17 15:05:18 +0100755
756 cherrypy.response.status = (
757 HTTPStatus.ACCEPTED.value
758 if not done
garciadeblasaca8cb52023-12-21 16:28:15 +0100759 else (
760 HTTPStatus.OK.value
761 if outdata is not None
762 else HTTPStatus.NO_CONTENT.value
763 )
sousaedu80135b92021-02-17 15:05:18 +0100764 )
765
tierno1d213f42020-04-24 14:02:51 +0000766 return self._format_out(outdata, token_info, _format)
767 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +0100768 if isinstance(
769 e,
770 (
771 RoException,
772 NsException,
773 DbException,
774 FsException,
775 MsgException,
776 AuthException,
777 ValidationError,
778 ),
779 ):
tierno1d213f42020-04-24 14:02:51 +0000780 http_code_value = cherrypy.response.status = e.http_code.value
781 http_code_name = e.http_code.name
782 cherrypy.log("Exception {}".format(e))
783 else:
garciadeblasaca8cb52023-12-21 16:28:15 +0100784 http_code_value = cherrypy.response.status = (
785 HTTPStatus.BAD_REQUEST.value
786 ) # INTERNAL_SERVER_ERROR
tierno1d213f42020-04-24 14:02:51 +0000787 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
788 http_code_name = HTTPStatus.BAD_REQUEST.name
sousaedu80135b92021-02-17 15:05:18 +0100789
tierno1d213f42020-04-24 14:02:51 +0000790 if hasattr(outdata, "close"): # is an open file
791 outdata.close()
sousaedu80135b92021-02-17 15:05:18 +0100792
tierno1d213f42020-04-24 14:02:51 +0000793 error_text = str(e)
794 rollback.reverse()
sousaedu80135b92021-02-17 15:05:18 +0100795
tierno1d213f42020-04-24 14:02:51 +0000796 for rollback_item in rollback:
797 try:
798 if rollback_item.get("operation") == "set":
sousaedu80135b92021-02-17 15:05:18 +0100799 self.ns.db.set_one(
800 rollback_item["topic"],
801 {"_id": rollback_item["_id"]},
802 rollback_item["content"],
803 fail_on_empty=False,
804 )
tierno1d213f42020-04-24 14:02:51 +0000805 else:
sousaedu80135b92021-02-17 15:05:18 +0100806 self.ns.db.del_one(
807 rollback_item["topic"],
808 {"_id": rollback_item["_id"]},
809 fail_on_empty=False,
810 )
tierno1d213f42020-04-24 14:02:51 +0000811 except Exception as e2:
sousaedu80135b92021-02-17 15:05:18 +0100812 rollback_error_text = "Rollback Exception {}: {}".format(
813 rollback_item, e2
814 )
tierno1d213f42020-04-24 14:02:51 +0000815 cherrypy.log(rollback_error_text)
816 error_text += ". " + rollback_error_text
sousaedu80135b92021-02-17 15:05:18 +0100817
tierno1d213f42020-04-24 14:02:51 +0000818 # if isinstance(e, MsgException):
819 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
820 # engine_topic[:-1], method, error_text)
821 problem_details = {
822 "code": http_code_name,
823 "status": http_code_value,
824 "detail": error_text,
825 }
sousaedu80135b92021-02-17 15:05:18 +0100826
tierno1d213f42020-04-24 14:02:51 +0000827 return self._format_out(problem_details, token_info)
828 # raise cherrypy.HTTPError(e.http_code.value, str(e))
829 finally:
830 if token_info:
831 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
832 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
833 if outdata.get(logging_id):
sousaedu80135b92021-02-17 15:05:18 +0100834 cherrypy.request.login += ";{}={}".format(
835 logging_id, outdata[logging_id][:36]
836 )
tierno1d213f42020-04-24 14:02:51 +0000837
838
839def _start_service():
840 """
841 Callback function called when cherrypy.engine starts
842 Override configuration with env variables
843 Set database, storage, message configuration
844 Init database with admin/admin user password
845 """
tierno70eeb182020-10-19 16:38:00 +0000846 global ro_server, vim_admin_thread
tierno1d213f42020-04-24 14:02:51 +0000847 # global vim_threads
848 cherrypy.log.error("Starting osm_ng_ro")
849 # update general cherrypy configuration
850 update_dict = {}
sousaedu80135b92021-02-17 15:05:18 +0100851 engine_config = cherrypy.tree.apps["/ro"].config
tierno1d213f42020-04-24 14:02:51 +0000852
tierno1d213f42020-04-24 14:02:51 +0000853 for k, v in environ.items():
854 if not k.startswith("OSMRO_"):
855 continue
sousaedu80135b92021-02-17 15:05:18 +0100856
tierno1d213f42020-04-24 14:02:51 +0000857 k1, _, k2 = k[6:].lower().partition("_")
sousaedu80135b92021-02-17 15:05:18 +0100858
tierno1d213f42020-04-24 14:02:51 +0000859 if not k2:
860 continue
sousaedu80135b92021-02-17 15:05:18 +0100861
tierno1d213f42020-04-24 14:02:51 +0000862 try:
863 if k1 in ("server", "test", "auth", "log"):
864 # update [global] configuration
sousaedu80135b92021-02-17 15:05:18 +0100865 update_dict[k1 + "." + k2] = yaml.safe_load(v)
tierno1d213f42020-04-24 14:02:51 +0000866 elif k1 == "static":
867 # update [/static] configuration
868 engine_config["/static"]["tools.staticdir." + k2] = yaml.safe_load(v)
869 elif k1 == "tools":
870 # update [/] configuration
sousaedu80135b92021-02-17 15:05:18 +0100871 engine_config["/"]["tools." + k2.replace("_", ".")] = yaml.safe_load(v)
aticig1ac189e2022-06-30 19:29:04 +0300872 elif k1 in ("message", "database", "storage", "authentication", "period"):
tierno70eeb182020-10-19 16:38:00 +0000873 engine_config[k1][k2] = yaml.safe_load(v)
tierno1d213f42020-04-24 14:02:51 +0000874
875 except Exception as e:
876 raise RoException("Cannot load env '{}': {}".format(k, e))
877
878 if update_dict:
879 cherrypy.config.update(update_dict)
880 engine_config["global"].update(update_dict)
881
882 # logging cherrypy
sousaedu80135b92021-02-17 15:05:18 +0100883 log_format_simple = (
884 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
885 )
886 log_formatter_simple = logging.Formatter(
887 log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S"
888 )
tierno1d213f42020-04-24 14:02:51 +0000889 logger_server = logging.getLogger("cherrypy.error")
890 logger_access = logging.getLogger("cherrypy.access")
891 logger_cherry = logging.getLogger("cherrypy")
sousaedue493e9b2021-02-09 15:30:01 +0100892 logger = logging.getLogger("ro")
tierno1d213f42020-04-24 14:02:51 +0000893
894 if "log.file" in engine_config["global"]:
sousaedu80135b92021-02-17 15:05:18 +0100895 file_handler = logging.handlers.RotatingFileHandler(
896 engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0
897 )
tierno1d213f42020-04-24 14:02:51 +0000898 file_handler.setFormatter(log_formatter_simple)
899 logger_cherry.addHandler(file_handler)
sousaedue493e9b2021-02-09 15:30:01 +0100900 logger.addHandler(file_handler)
sousaedu80135b92021-02-17 15:05:18 +0100901
tierno1d213f42020-04-24 14:02:51 +0000902 # log always to standard output
sousaedu80135b92021-02-17 15:05:18 +0100903 for format_, logger in {
904 "ro.server %(filename)s:%(lineno)s": logger_server,
905 "ro.access %(filename)s:%(lineno)s": logger_access,
906 "%(name)s %(filename)s:%(lineno)s": logger,
907 }.items():
tierno1d213f42020-04-24 14:02:51 +0000908 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
sousaedu80135b92021-02-17 15:05:18 +0100909 log_formatter_cherry = logging.Formatter(
910 log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S"
911 )
tierno1d213f42020-04-24 14:02:51 +0000912 str_handler = logging.StreamHandler()
913 str_handler.setFormatter(log_formatter_cherry)
914 logger.addHandler(str_handler)
915
916 if engine_config["global"].get("log.level"):
917 logger_cherry.setLevel(engine_config["global"]["log.level"])
sousaedue493e9b2021-02-09 15:30:01 +0100918 logger.setLevel(engine_config["global"]["log.level"])
sousaedu80135b92021-02-17 15:05:18 +0100919
tierno1d213f42020-04-24 14:02:51 +0000920 # logging other modules
sousaedu80135b92021-02-17 15:05:18 +0100921 for k1, logname in {
922 "message": "ro.msg",
923 "database": "ro.db",
924 "storage": "ro.fs",
925 }.items():
tierno1d213f42020-04-24 14:02:51 +0000926 engine_config[k1]["logger_name"] = logname
927 logger_module = logging.getLogger(logname)
sousaedu80135b92021-02-17 15:05:18 +0100928
tierno1d213f42020-04-24 14:02:51 +0000929 if "logfile" in engine_config[k1]:
sousaedu80135b92021-02-17 15:05:18 +0100930 file_handler = logging.handlers.RotatingFileHandler(
931 engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0
932 )
tierno1d213f42020-04-24 14:02:51 +0000933 file_handler.setFormatter(log_formatter_simple)
934 logger_module.addHandler(file_handler)
sousaedu80135b92021-02-17 15:05:18 +0100935
tierno1d213f42020-04-24 14:02:51 +0000936 if "loglevel" in engine_config[k1]:
937 logger_module.setLevel(engine_config[k1]["loglevel"])
938 # TODO add more entries, e.g.: storage
939
940 engine_config["assignment"] = {}
941 # ^ each VIM, SDNc will be assigned one worker id. Ns class will add items and VimThread will auto-assign
sousaedu80135b92021-02-17 15:05:18 +0100942 cherrypy.tree.apps["/ro"].root.ns.start(engine_config)
943 cherrypy.tree.apps["/ro"].root.authenticator.start(engine_config)
944 cherrypy.tree.apps["/ro"].root.ns.init_db(target_version=database_version)
tierno1d213f42020-04-24 14:02:51 +0000945
946 # # start subscriptions thread:
tierno70eeb182020-10-19 16:38:00 +0000947 vim_admin_thread = VimAdminThread(config=engine_config, engine=ro_server.ns)
948 vim_admin_thread.start()
Gulsum Aticid586d892023-02-13 18:40:03 +0300949 start_monitoring(config=engine_config)
950
tierno1d213f42020-04-24 14:02:51 +0000951 # # Do not capture except SubscriptionException
952
tierno70eeb182020-10-19 16:38:00 +0000953 # backend = engine_config["authentication"]["backend"]
954 # cherrypy.log.error("Starting OSM NBI Version '{} {}' with '{}' authentication backend"
955 # .format(ro_version, ro_version_date, backend))
tierno1d213f42020-04-24 14:02:51 +0000956
957
958def _stop_service():
959 """
960 Callback function called when cherrypy.engine stops
961 TODO: Ending database connections.
962 """
tierno70eeb182020-10-19 16:38:00 +0000963 global vim_admin_thread
sousaedu80135b92021-02-17 15:05:18 +0100964
tierno70eeb182020-10-19 16:38:00 +0000965 # terminate vim_admin_thread
966 if vim_admin_thread:
967 vim_admin_thread.terminate()
Gulsum Aticid586d892023-02-13 18:40:03 +0300968 stop_monitoring()
tierno70eeb182020-10-19 16:38:00 +0000969 vim_admin_thread = None
sousaedu80135b92021-02-17 15:05:18 +0100970 cherrypy.tree.apps["/ro"].root.ns.stop()
tierno1d213f42020-04-24 14:02:51 +0000971 cherrypy.log.error("Stopping osm_ng_ro")
972
973
974def ro_main(config_file):
975 global ro_server
sousaedu80135b92021-02-17 15:05:18 +0100976
tierno1d213f42020-04-24 14:02:51 +0000977 ro_server = Server()
sousaedu80135b92021-02-17 15:05:18 +0100978 cherrypy.engine.subscribe("start", _start_service)
979 cherrypy.engine.subscribe("stop", _stop_service)
980 cherrypy.quickstart(ro_server, "/ro", config_file)
tierno1d213f42020-04-24 14:02:51 +0000981
982
983def usage():
sousaedu80135b92021-02-17 15:05:18 +0100984 print(
985 """Usage: {} [options]
tierno1d213f42020-04-24 14:02:51 +0000986 -c|--config [configuration_file]: loads the configuration file (default: ./ro.cfg)
987 -h|--help: shows this help
sousaedu80135b92021-02-17 15:05:18 +0100988 """.format(
989 sys.argv[0]
990 )
991 )
tierno1d213f42020-04-24 14:02:51 +0000992 # --log-socket-host HOST: send logs to this host")
993 # --log-socket-port PORT: send logs using this port (default: 9022)")
994
995
sousaedu80135b92021-02-17 15:05:18 +0100996if __name__ == "__main__":
tierno1d213f42020-04-24 14:02:51 +0000997 try:
998 # load parameters and configuration
999 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
1000 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1001 config_file = None
sousaedu80135b92021-02-17 15:05:18 +01001002
tierno1d213f42020-04-24 14:02:51 +00001003 for o, a in opts:
1004 if o in ("-h", "--help"):
1005 usage()
1006 sys.exit()
1007 elif o in ("-c", "--config"):
1008 config_file = a
1009 else:
aticig7b521f72022-07-15 00:43:09 +03001010 raise ValueError("Unhandled option")
sousaedu80135b92021-02-17 15:05:18 +01001011
tierno1d213f42020-04-24 14:02:51 +00001012 if config_file:
1013 if not path.isfile(config_file):
sousaedu80135b92021-02-17 15:05:18 +01001014 print(
1015 "configuration file '{}' that not exist".format(config_file),
1016 file=sys.stderr,
1017 )
tierno1d213f42020-04-24 14:02:51 +00001018 exit(1)
1019 else:
sousaedu80135b92021-02-17 15:05:18 +01001020 for config_file in (
1021 path.dirname(__file__) + "/ro.cfg",
1022 "./ro.cfg",
1023 "/etc/osm/ro.cfg",
1024 ):
tierno1d213f42020-04-24 14:02:51 +00001025 if path.isfile(config_file):
1026 break
1027 else:
sousaedu80135b92021-02-17 15:05:18 +01001028 print(
1029 "No configuration file 'ro.cfg' found neither at local folder nor at /etc/osm/",
1030 file=sys.stderr,
1031 )
tierno1d213f42020-04-24 14:02:51 +00001032 exit(1)
sousaedu80135b92021-02-17 15:05:18 +01001033
tierno1d213f42020-04-24 14:02:51 +00001034 ro_main(config_file)
tierno70eeb182020-10-19 16:38:00 +00001035 except KeyboardInterrupt:
1036 print("KeyboardInterrupt. Finishing", file=sys.stderr)
tierno1d213f42020-04-24 14:02:51 +00001037 except getopt.GetoptError as e:
1038 print(str(e), file=sys.stderr)
1039 # usage()
1040 exit(1)