blob: 47296dc35de3f9bf960ab85ca475aba40fcff47e [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
38from osm_ng_ro.ns import Ns, NsException
39from osm_ng_ro.validation import ValidationError
40from osm_ng_ro.vim_admin import VimAdminThread
41import yaml
42
tierno1d213f42020-04-24 14:02:51 +000043
44__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
sousaedu80135b92021-02-17 15:05:18 +010045__version__ = "0.1." # file version, not NBI version
tierno1d213f42020-04-24 14:02:51 +000046version_date = "May 2020"
47
sousaedu80135b92021-02-17 15:05:18 +010048database_version = "1.2"
49auth_database_version = "1.0"
50ro_server = None # instance of Server class
51vim_admin_thread = None # instance of VimAdminThread class
tierno70eeb182020-10-19 16:38:00 +000052
tierno1d213f42020-04-24 14:02:51 +000053# vim_threads = None # instance of VimThread class
54
55"""
56RO North Bound Interface
57URL: /ro GET POST PUT DELETE PATCH
58 /ns/v1/deploy O
59 /<nsrs_id> O O O
60 /<action_id> O
61 /cancel O
62
63"""
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 },
tierno1d213f42020-04-24 14:02:51 +0000110 "deploy": {
111 "METHODS": ("GET",),
112 "ROLE_PERMISSION": "deploy:",
113 "<ID>": {
114 "METHODS": ("GET", "POST", "DELETE"),
115 "ROLE_PERMISSION": "deploy:id:",
116 "<ID>": {
117 "METHODS": ("GET",),
118 "ROLE_PERMISSION": "deploy:id:id:",
119 "cancel": {
120 "METHODS": ("POST",),
121 "ROLE_PERMISSION": "deploy:id:id:cancel",
sousaedu80135b92021-02-17 15:05:18 +0100122 },
123 },
124 },
tierno1d213f42020-04-24 14:02:51 +0000125 },
palaciosj8f2060b2022-02-24 12:05:59 +0000126 "recreate": {
127 "<ID>": {
128 "METHODS": ("POST"),
129 "ROLE_PERMISSION": "recreate:id:",
130 "<ID>": {
131 "METHODS": ("GET",),
132 "ROLE_PERMISSION": "recreate:id:id:",
133 },
134 },
135 },
elumalai8658c2c2022-04-28 19:09:31 +0530136 "migrate": {
137 "<ID>": {
138 "METHODS": ("POST"),
139 "ROLE_PERMISSION": "migrate:id:",
140 "<ID>": {
141 "METHODS": ("GET",),
142 "ROLE_PERMISSION": "migrate:id:id:",
143 },
144 },
145 },
sritharan29a4c1a2022-05-05 12:15:04 +0000146 "verticalscale": {
147 "<ID>": {
148 "METHODS": ("POST"),
149 "ROLE_PERMISSION": "verticalscale:id:",
150 "<ID>": {
151 "METHODS": ("GET",),
152 "ROLE_PERMISSION": "verticalscale:id:id:",
153 },
154 },
155 },
tierno1d213f42020-04-24 14:02:51 +0000156 }
157 },
158}
159
160
161class RoException(Exception):
tierno1d213f42020-04-24 14:02:51 +0000162 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
163 Exception.__init__(self, message)
164 self.http_code = http_code
165
166
167class AuthException(RoException):
168 pass
169
170
171class Authenticator:
tierno1d213f42020-04-24 14:02:51 +0000172 def __init__(self, valid_url_methods, valid_query_string):
173 self.valid_url_methods = valid_url_methods
174 self.valid_query_string = valid_query_string
175
176 def authorize(self, *args, **kwargs):
177 return {"token": "ok", "id": "ok"}
sousaedu80135b92021-02-17 15:05:18 +0100178
tierno1d213f42020-04-24 14:02:51 +0000179 def new_token(self, token_info, indata, remote):
sousaedu80135b92021-02-17 15:05:18 +0100180 return {"token": "ok", "id": "ok", "remote": remote}
tierno1d213f42020-04-24 14:02:51 +0000181
182 def del_token(self, token_id):
183 pass
184
185 def start(self, engine_config):
186 pass
187
188
189class Server(object):
190 instance = 0
191 # to decode bytes to str
192 reader = getreader("utf-8")
193
194 def __init__(self):
195 self.instance += 1
196 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
197 self.ns = Ns()
198 self.map_operation = {
199 "token:post": self.new_token,
200 "token:id:delete": self.del_token,
201 "deploy:get": self.ns.get_deploy,
202 "deploy:id:get": self.ns.get_actions,
203 "deploy:id:post": self.ns.deploy,
204 "deploy:id:delete": self.ns.delete,
205 "deploy:id:id:get": self.ns.status,
206 "deploy:id:id:cancel:post": self.ns.cancel,
k4.rahul78f474e2022-05-02 15:47:57 +0000207 "rebuild:id:post": self.ns.rebuild_start_stop,
208 "start:id:post": self.ns.rebuild_start_stop,
209 "stop:id:post": self.ns.rebuild_start_stop,
palaciosj8f2060b2022-02-24 12:05:59 +0000210 "recreate:id:post": self.ns.recreate,
211 "recreate:id:id:get": self.ns.recreate_status,
elumalai8658c2c2022-04-28 19:09:31 +0530212 "migrate:id:post": self.ns.migrate,
sritharan29a4c1a2022-05-05 12:15:04 +0000213 "verticalscale:id:post": self.ns.verticalscale,
tierno1d213f42020-04-24 14:02:51 +0000214 }
215
216 def _format_in(self, kwargs):
Gulsum Atici2f995052022-11-23 16:06:40 +0300217 error_text = ""
tierno1d213f42020-04-24 14:02:51 +0000218 try:
219 indata = None
sousaedu80135b92021-02-17 15:05:18 +0100220
tierno1d213f42020-04-24 14:02:51 +0000221 if cherrypy.request.body.length:
222 error_text = "Invalid input format "
223
224 if "Content-Type" in cherrypy.request.headers:
225 if "application/json" in cherrypy.request.headers["Content-Type"]:
226 error_text = "Invalid json format "
227 indata = json.load(self.reader(cherrypy.request.body))
228 cherrypy.request.headers.pop("Content-File-MD5", None)
229 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
230 error_text = "Invalid yaml format "
aticig7b521f72022-07-15 00:43:09 +0300231 indata = yaml.safe_load(cherrypy.request.body)
tierno1d213f42020-04-24 14:02:51 +0000232 cherrypy.request.headers.pop("Content-File-MD5", None)
sousaedu80135b92021-02-17 15:05:18 +0100233 elif (
234 "application/binary" in cherrypy.request.headers["Content-Type"]
235 or "application/gzip"
236 in cherrypy.request.headers["Content-Type"]
237 or "application/zip" in cherrypy.request.headers["Content-Type"]
238 or "text/plain" in cherrypy.request.headers["Content-Type"]
239 ):
tierno1d213f42020-04-24 14:02:51 +0000240 indata = cherrypy.request.body # .read()
sousaedu80135b92021-02-17 15:05:18 +0100241 elif (
242 "multipart/form-data"
243 in cherrypy.request.headers["Content-Type"]
244 ):
tierno1d213f42020-04-24 14:02:51 +0000245 if "descriptor_file" in kwargs:
246 filecontent = kwargs.pop("descriptor_file")
sousaedu80135b92021-02-17 15:05:18 +0100247
tierno1d213f42020-04-24 14:02:51 +0000248 if not filecontent.file:
sousaedu80135b92021-02-17 15:05:18 +0100249 raise RoException(
250 "empty file or content", HTTPStatus.BAD_REQUEST
251 )
252
tierno1d213f42020-04-24 14:02:51 +0000253 indata = filecontent.file # .read()
sousaedu80135b92021-02-17 15:05:18 +0100254
tierno1d213f42020-04-24 14:02:51 +0000255 if filecontent.content_type.value:
sousaedu80135b92021-02-17 15:05:18 +0100256 cherrypy.request.headers[
257 "Content-Type"
258 ] = filecontent.content_type.value
tierno1d213f42020-04-24 14:02:51 +0000259 else:
260 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
261 # "Only 'Content-Type' of type 'application/json' or
262 # 'application/yaml' for input format are available")
263 error_text = "Invalid yaml format "
aticig7b521f72022-07-15 00:43:09 +0300264 indata = yaml.safe_load(cherrypy.request.body)
tierno1d213f42020-04-24 14:02:51 +0000265 cherrypy.request.headers.pop("Content-File-MD5", None)
266 else:
267 error_text = "Invalid yaml format "
aticig7b521f72022-07-15 00:43:09 +0300268 indata = yaml.safe_load(cherrypy.request.body)
tierno1d213f42020-04-24 14:02:51 +0000269 cherrypy.request.headers.pop("Content-File-MD5", None)
sousaedu80135b92021-02-17 15:05:18 +0100270
tierno1d213f42020-04-24 14:02:51 +0000271 if not indata:
272 indata = {}
273
274 format_yaml = False
275 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
276 format_yaml = True
277
278 for k, v in kwargs.items():
279 if isinstance(v, str):
280 if v == "":
281 kwargs[k] = None
282 elif format_yaml:
283 try:
aticig7b521f72022-07-15 00:43:09 +0300284 kwargs[k] = yaml.safe_load(v)
285 except Exception as yaml_error:
286 logging.exception(
287 f"{yaml_error} occured while parsing the yaml"
288 )
sousaedu80135b92021-02-17 15:05:18 +0100289 elif (
290 k.endswith(".gt")
291 or k.endswith(".lt")
292 or k.endswith(".gte")
293 or k.endswith(".lte")
294 ):
tierno1d213f42020-04-24 14:02:51 +0000295 try:
296 kwargs[k] = int(v)
297 except Exception:
298 try:
299 kwargs[k] = float(v)
aticig7b521f72022-07-15 00:43:09 +0300300 except Exception as keyword_error:
301 logging.exception(
302 f"{keyword_error} occured while getting the keyword arguments"
303 )
tierno1d213f42020-04-24 14:02:51 +0000304 elif v.find(",") > 0:
305 kwargs[k] = v.split(",")
306 elif isinstance(v, (list, tuple)):
307 for index in range(0, len(v)):
308 if v[index] == "":
309 v[index] = None
310 elif format_yaml:
311 try:
aticig7b521f72022-07-15 00:43:09 +0300312 v[index] = yaml.safe_load(v[index])
313 except Exception as error:
314 logging.exception(
315 f"{error} occured while parsing the yaml"
316 )
tierno1d213f42020-04-24 14:02:51 +0000317
318 return indata
319 except (ValueError, yaml.YAMLError) as exc:
320 raise RoException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
321 except KeyError as exc:
322 raise RoException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
323 except Exception as exc:
324 raise RoException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
325
326 @staticmethod
327 def _format_out(data, token_info=None, _format=None):
328 """
329 return string of dictionary data according to requested json, yaml, xml. By default json
330 :param data: response to be sent. Can be a dict, text or file
331 :param token_info: Contains among other username and project
332 :param _format: The format to be set as Content-Type if data is a file
333 :return: None
334 """
335 accept = cherrypy.request.headers.get("Accept")
sousaedu80135b92021-02-17 15:05:18 +0100336
tierno1d213f42020-04-24 14:02:51 +0000337 if data is None:
338 if accept and "text/html" in accept:
sousaedu80135b92021-02-17 15:05:18 +0100339 return html.format(
340 data, cherrypy.request, cherrypy.response, token_info
341 )
342
tierno1d213f42020-04-24 14:02:51 +0000343 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
344 return
345 elif hasattr(data, "read"): # file object
346 if _format:
347 cherrypy.response.headers["Content-Type"] = _format
348 elif "b" in data.mode: # binariy asssumig zip
sousaedu80135b92021-02-17 15:05:18 +0100349 cherrypy.response.headers["Content-Type"] = "application/zip"
tierno1d213f42020-04-24 14:02:51 +0000350 else:
sousaedu80135b92021-02-17 15:05:18 +0100351 cherrypy.response.headers["Content-Type"] = "text/plain"
352
tierno1d213f42020-04-24 14:02:51 +0000353 # TODO check that cherrypy close file. If not implement pending things to close per thread next
354 return data
sousaedu80135b92021-02-17 15:05:18 +0100355
tierno1d213f42020-04-24 14:02:51 +0000356 if accept:
357 if "application/json" in accept:
sousaedu80135b92021-02-17 15:05:18 +0100358 cherrypy.response.headers[
359 "Content-Type"
360 ] = "application/json; charset=utf-8"
tierno1d213f42020-04-24 14:02:51 +0000361 a = json.dumps(data, indent=4) + "\n"
sousaedu80135b92021-02-17 15:05:18 +0100362
tierno1d213f42020-04-24 14:02:51 +0000363 return a.encode("utf8")
364 elif "text/html" in accept:
sousaedu80135b92021-02-17 15:05:18 +0100365 return html.format(
366 data, cherrypy.request, cherrypy.response, token_info
367 )
368 elif (
369 "application/yaml" in accept
370 or "*/*" in accept
371 or "text/plain" in accept
372 ):
tierno1d213f42020-04-24 14:02:51 +0000373 pass
374 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
375 elif cherrypy.response.status >= 400:
sousaedu80135b92021-02-17 15:05:18 +0100376 raise cherrypy.HTTPError(
377 HTTPStatus.NOT_ACCEPTABLE.value,
378 "Only 'Accept' of type 'application/json' or 'application/yaml' "
379 "for output format are available",
380 )
381
382 cherrypy.response.headers["Content-Type"] = "application/yaml"
383
384 return yaml.safe_dump(
385 data,
386 explicit_start=True,
387 indent=4,
388 default_flow_style=False,
389 tags=False,
390 encoding="utf-8",
391 allow_unicode=True,
392 ) # , canonical=True, default_style='"'
tierno1d213f42020-04-24 14:02:51 +0000393
394 @cherrypy.expose
395 def index(self, *args, **kwargs):
396 token_info = None
sousaedu80135b92021-02-17 15:05:18 +0100397
tierno1d213f42020-04-24 14:02:51 +0000398 try:
399 if cherrypy.request.method == "GET":
400 token_info = self.authenticator.authorize()
sousaedu80135b92021-02-17 15:05:18 +0100401 outdata = token_info # Home page
tierno1d213f42020-04-24 14:02:51 +0000402 else:
sousaedu80135b92021-02-17 15:05:18 +0100403 raise cherrypy.HTTPError(
404 HTTPStatus.METHOD_NOT_ALLOWED.value,
405 "Method {} not allowed for tokens".format(cherrypy.request.method),
406 )
tierno1d213f42020-04-24 14:02:51 +0000407
408 return self._format_out(outdata, token_info)
tierno1d213f42020-04-24 14:02:51 +0000409 except (NsException, AuthException) as e:
410 # cherrypy.log("index Exception {}".format(e))
411 cherrypy.response.status = e.http_code.value
sousaedu80135b92021-02-17 15:05:18 +0100412
tierno1d213f42020-04-24 14:02:51 +0000413 return self._format_out("Welcome to OSM!", token_info)
414
415 @cherrypy.expose
416 def version(self, *args, **kwargs):
417 # TODO consider to remove and provide version using the static version file
418 try:
419 if cherrypy.request.method != "GET":
sousaedu80135b92021-02-17 15:05:18 +0100420 raise RoException(
421 "Only method GET is allowed",
422 HTTPStatus.METHOD_NOT_ALLOWED,
423 )
tierno1d213f42020-04-24 14:02:51 +0000424 elif args or kwargs:
sousaedu80135b92021-02-17 15:05:18 +0100425 raise RoException(
426 "Invalid URL or query string for version",
427 HTTPStatus.METHOD_NOT_ALLOWED,
428 )
429
tierno1d213f42020-04-24 14:02:51 +0000430 # TODO include version of other modules, pick up from some kafka admin message
431 osm_ng_ro_version = {"version": ro_version, "date": ro_version_date}
sousaedu80135b92021-02-17 15:05:18 +0100432
tierno1d213f42020-04-24 14:02:51 +0000433 return self._format_out(osm_ng_ro_version)
434 except RoException as e:
435 cherrypy.response.status = e.http_code.value
436 problem_details = {
437 "code": e.http_code.name,
438 "status": e.http_code.value,
439 "detail": str(e),
440 }
sousaedu80135b92021-02-17 15:05:18 +0100441
tierno1d213f42020-04-24 14:02:51 +0000442 return self._format_out(problem_details, None)
443
444 def new_token(self, engine_session, indata, *args, **kwargs):
445 token_info = None
446
447 try:
448 token_info = self.authenticator.authorize()
449 except Exception:
450 token_info = None
sousaedu80135b92021-02-17 15:05:18 +0100451
tierno1d213f42020-04-24 14:02:51 +0000452 if kwargs:
453 indata.update(kwargs)
sousaedu80135b92021-02-17 15:05:18 +0100454
tierno1d213f42020-04-24 14:02:51 +0000455 # This is needed to log the user when authentication fails
456 cherrypy.request.login = "{}".format(indata.get("username", "-"))
sousaedu80135b92021-02-17 15:05:18 +0100457 token_info = self.authenticator.new_token(
458 token_info, indata, cherrypy.request.remote
459 )
460 cherrypy.session["Authorization"] = token_info["id"]
tierno1d213f42020-04-24 14:02:51 +0000461 self._set_location_header("admin", "v1", "tokens", token_info["id"])
462 # for logging
463
464 # cherrypy.response.cookie["Authorization"] = outdata["id"]
465 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
sousaedu80135b92021-02-17 15:05:18 +0100466
tierno1d213f42020-04-24 14:02:51 +0000467 return token_info, token_info["id"], True
468
469 def del_token(self, engine_session, indata, version, _id, *args, **kwargs):
470 token_id = _id
sousaedu80135b92021-02-17 15:05:18 +0100471
tierno1d213f42020-04-24 14:02:51 +0000472 if not token_id and "id" in kwargs:
473 token_id = kwargs["id"]
474 elif not token_id:
475 token_info = self.authenticator.authorize()
476 # for logging
477 token_id = token_info["id"]
sousaedu80135b92021-02-17 15:05:18 +0100478
tierno1d213f42020-04-24 14:02:51 +0000479 self.authenticator.del_token(token_id)
480 token_info = None
sousaedu80135b92021-02-17 15:05:18 +0100481 cherrypy.session["Authorization"] = "logout"
tierno1d213f42020-04-24 14:02:51 +0000482 # cherrypy.response.cookie["Authorization"] = token_id
483 # cherrypy.response.cookie["Authorization"]['expires'] = 0
sousaedu80135b92021-02-17 15:05:18 +0100484
tierno1d213f42020-04-24 14:02:51 +0000485 return None, None, True
sousaedu80135b92021-02-17 15:05:18 +0100486
tierno1d213f42020-04-24 14:02:51 +0000487 @cherrypy.expose
488 def test(self, *args, **kwargs):
sousaedu80135b92021-02-17 15:05:18 +0100489 if not cherrypy.config.get("server.enable_test") or (
490 isinstance(cherrypy.config["server.enable_test"], str)
491 and cherrypy.config["server.enable_test"].lower() == "false"
492 ):
tierno1d213f42020-04-24 14:02:51 +0000493 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value
tierno1d213f42020-04-24 14:02:51 +0000494
sousaedu80135b92021-02-17 15:05:18 +0100495 return "test URL is disabled"
496
497 thread_info = None
498
499 if args and args[0] == "help":
500 return (
501 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
502 "sleep/<time>\nmessage/topic\n</pre></html>"
503 )
tierno1d213f42020-04-24 14:02:51 +0000504 elif args and args[0] == "init":
505 try:
506 # self.ns.load_dbase(cherrypy.request.app.config)
507 self.ns.create_admin()
sousaedu80135b92021-02-17 15:05:18 +0100508
tierno1d213f42020-04-24 14:02:51 +0000509 return "Done. User 'admin', password 'admin' created"
510 except Exception:
511 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
sousaedu80135b92021-02-17 15:05:18 +0100512
tierno1d213f42020-04-24 14:02:51 +0000513 return self._format_out("Database already initialized")
514 elif args and args[0] == "file":
sousaedu80135b92021-02-17 15:05:18 +0100515 return cherrypy.lib.static.serve_file(
516 cherrypy.tree.apps["/ro"].config["storage"]["path"] + "/" + args[1],
517 "text/plain",
518 "attachment",
519 )
tierno1d213f42020-04-24 14:02:51 +0000520 elif args and args[0] == "file2":
sousaedu80135b92021-02-17 15:05:18 +0100521 f_path = cherrypy.tree.apps["/ro"].config["storage"]["path"] + "/" + args[1]
tierno1d213f42020-04-24 14:02:51 +0000522 f = open(f_path, "r")
523 cherrypy.response.headers["Content-type"] = "text/plain"
524 return f
525
526 elif len(args) == 2 and args[0] == "db-clear":
527 deleted_info = self.ns.db.del_list(args[1], kwargs)
528 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
529 elif len(args) and args[0] == "fs-clear":
530 if len(args) >= 2:
531 folders = (args[1],)
532 else:
533 folders = self.ns.fs.dir_ls(".")
sousaedu80135b92021-02-17 15:05:18 +0100534
tierno1d213f42020-04-24 14:02:51 +0000535 for folder in folders:
536 self.ns.fs.file_delete(folder)
sousaedu80135b92021-02-17 15:05:18 +0100537
tierno1d213f42020-04-24 14:02:51 +0000538 return ",".join(folders) + " folders deleted\n"
539 elif args and args[0] == "login":
540 if not cherrypy.request.headers.get("Authorization"):
sousaedu80135b92021-02-17 15:05:18 +0100541 cherrypy.response.headers[
542 "WWW-Authenticate"
543 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
tierno1d213f42020-04-24 14:02:51 +0000544 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
545 elif args and args[0] == "login2":
546 if not cherrypy.request.headers.get("Authorization"):
sousaedu80135b92021-02-17 15:05:18 +0100547 cherrypy.response.headers[
548 "WWW-Authenticate"
549 ] = 'Bearer realm="Access to OSM site"'
tierno1d213f42020-04-24 14:02:51 +0000550 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
551 elif args and args[0] == "sleep":
552 sleep_time = 5
sousaedu80135b92021-02-17 15:05:18 +0100553
tierno1d213f42020-04-24 14:02:51 +0000554 try:
555 sleep_time = int(args[1])
556 except Exception:
557 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
558 return self._format_out("Database already initialized")
sousaedu80135b92021-02-17 15:05:18 +0100559
tierno1d213f42020-04-24 14:02:51 +0000560 thread_info = cherrypy.thread_data
561 print(thread_info)
562 time.sleep(sleep_time)
563 # thread_info
564 elif len(args) >= 2 and args[0] == "message":
565 main_topic = args[1]
566 return_text = "<html><pre>{} ->\n".format(main_topic)
sousaedu80135b92021-02-17 15:05:18 +0100567
tierno1d213f42020-04-24 14:02:51 +0000568 try:
sousaedu80135b92021-02-17 15:05:18 +0100569 if cherrypy.request.method == "POST":
aticig7b521f72022-07-15 00:43:09 +0300570 to_send = yaml.safe_load(cherrypy.request.body)
tierno1d213f42020-04-24 14:02:51 +0000571 for k, v in to_send.items():
572 self.ns.msg.write(main_topic, k, v)
573 return_text += " {}: {}\n".format(k, v)
sousaedu80135b92021-02-17 15:05:18 +0100574 elif cherrypy.request.method == "GET":
tierno1d213f42020-04-24 14:02:51 +0000575 for k, v in kwargs.items():
aticig7b521f72022-07-15 00:43:09 +0300576 self.ns.msg.write(main_topic, k, yaml.safe_load(v))
577 return_text += " {}: {}\n".format(k, yaml.safe_load(v))
tierno1d213f42020-04-24 14:02:51 +0000578 except Exception as e:
579 return_text += "Error: " + str(e)
sousaedu80135b92021-02-17 15:05:18 +0100580
tierno1d213f42020-04-24 14:02:51 +0000581 return_text += "</pre></html>\n"
sousaedu80135b92021-02-17 15:05:18 +0100582
tierno1d213f42020-04-24 14:02:51 +0000583 return return_text
584
585 return_text = (
sousaedu80135b92021-02-17 15:05:18 +0100586 "<html><pre>\nheaders:\n args: {}\n".format(args)
587 + " kwargs: {}\n".format(kwargs)
588 + " headers: {}\n".format(cherrypy.request.headers)
589 + " path_info: {}\n".format(cherrypy.request.path_info)
590 + " query_string: {}\n".format(cherrypy.request.query_string)
591 + " session: {}\n".format(cherrypy.session)
592 + " cookie: {}\n".format(cherrypy.request.cookie)
593 + " method: {}\n".format(cherrypy.request.method)
594 + " session: {}\n".format(cherrypy.session.get("fieldname"))
595 + " body:\n"
596 )
tierno1d213f42020-04-24 14:02:51 +0000597 return_text += " length: {}\n".format(cherrypy.request.body.length)
sousaedu80135b92021-02-17 15:05:18 +0100598
tierno1d213f42020-04-24 14:02:51 +0000599 if cherrypy.request.body.length:
600 return_text += " content: {}\n".format(
sousaedu80135b92021-02-17 15:05:18 +0100601 str(
602 cherrypy.request.body.read(
603 int(cherrypy.request.headers.get("Content-Length", 0))
604 )
605 )
606 )
607
tierno1d213f42020-04-24 14:02:51 +0000608 if thread_info:
609 return_text += "thread: {}\n".format(thread_info)
sousaedu80135b92021-02-17 15:05:18 +0100610
tierno1d213f42020-04-24 14:02:51 +0000611 return_text += "</pre></html>"
sousaedu80135b92021-02-17 15:05:18 +0100612
tierno1d213f42020-04-24 14:02:51 +0000613 return return_text
614
615 @staticmethod
616 def _check_valid_url_method(method, *args):
617 if len(args) < 3:
sousaedu80135b92021-02-17 15:05:18 +0100618 raise RoException(
619 "URL must contain at least 'main_topic/version/topic'",
620 HTTPStatus.METHOD_NOT_ALLOWED,
621 )
tierno1d213f42020-04-24 14:02:51 +0000622
623 reference = valid_url_methods
624 for arg in args:
625 if arg is None:
626 break
sousaedu80135b92021-02-17 15:05:18 +0100627
tierno1d213f42020-04-24 14:02:51 +0000628 if not isinstance(reference, dict):
sousaedu80135b92021-02-17 15:05:18 +0100629 raise RoException(
630 "URL contains unexpected extra items '{}'".format(arg),
631 HTTPStatus.METHOD_NOT_ALLOWED,
632 )
tierno1d213f42020-04-24 14:02:51 +0000633
634 if arg in reference:
635 reference = reference[arg]
636 elif "<ID>" in reference:
637 reference = reference["<ID>"]
638 elif "*" in reference:
639 # reference = reference["*"]
640 break
641 else:
sousaedu80135b92021-02-17 15:05:18 +0100642 raise RoException(
643 "Unexpected URL item {}".format(arg),
644 HTTPStatus.METHOD_NOT_ALLOWED,
645 )
646
tierno1d213f42020-04-24 14:02:51 +0000647 if "TODO" in reference and method in reference["TODO"]:
sousaedu80135b92021-02-17 15:05:18 +0100648 raise RoException(
649 "Method {} not supported yet for this URL".format(method),
650 HTTPStatus.NOT_IMPLEMENTED,
651 )
tierno1d213f42020-04-24 14:02:51 +0000652 elif "METHODS" not in reference or method not in reference["METHODS"]:
sousaedu80135b92021-02-17 15:05:18 +0100653 raise RoException(
654 "Method {} not supported for this URL".format(method),
655 HTTPStatus.METHOD_NOT_ALLOWED,
656 )
657
tierno1d213f42020-04-24 14:02:51 +0000658 return reference["ROLE_PERMISSION"] + method.lower()
659
660 @staticmethod
661 def _set_location_header(main_topic, version, topic, id):
662 """
663 Insert response header Location with the URL of created item base on URL params
664 :param main_topic:
665 :param version:
666 :param topic:
667 :param id:
668 :return: None
669 """
670 # 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 +0100671 cherrypy.response.headers["Location"] = "/ro/{}/{}/{}/{}".format(
672 main_topic, version, topic, id
673 )
674
tierno1d213f42020-04-24 14:02:51 +0000675 return
676
677 @cherrypy.expose
sousaedu80135b92021-02-17 15:05:18 +0100678 def default(
679 self,
680 main_topic=None,
681 version=None,
682 topic=None,
683 _id=None,
684 _id2=None,
685 *args,
686 **kwargs,
687 ):
tierno1d213f42020-04-24 14:02:51 +0000688 token_info = None
689 outdata = None
690 _format = None
691 method = "DONE"
692 rollback = []
693 engine_session = None
sousaedu80135b92021-02-17 15:05:18 +0100694
tierno1d213f42020-04-24 14:02:51 +0000695 try:
696 if not main_topic or not version or not topic:
sousaedu80135b92021-02-17 15:05:18 +0100697 raise RoException(
698 "URL must contain at least 'main_topic/version/topic'",
699 HTTPStatus.METHOD_NOT_ALLOWED,
700 )
tierno1d213f42020-04-24 14:02:51 +0000701
sousaedu80135b92021-02-17 15:05:18 +0100702 if main_topic not in (
703 "admin",
704 "ns",
705 ):
706 raise RoException(
707 "URL main_topic '{}' not supported".format(main_topic),
708 HTTPStatus.METHOD_NOT_ALLOWED,
709 )
710
711 if version != "v1":
712 raise RoException(
713 "URL version '{}' not supported".format(version),
714 HTTPStatus.METHOD_NOT_ALLOWED,
715 )
716
717 if (
718 kwargs
719 and "METHOD" in kwargs
720 and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
721 ):
tierno1d213f42020-04-24 14:02:51 +0000722 method = kwargs.pop("METHOD")
723 else:
724 method = cherrypy.request.method
725
sousaedu80135b92021-02-17 15:05:18 +0100726 role_permission = self._check_valid_url_method(
727 method, main_topic, version, topic, _id, _id2, *args, **kwargs
728 )
tierno1d213f42020-04-24 14:02:51 +0000729 # skip token validation if requesting a token
730 indata = self._format_in(kwargs)
sousaedu80135b92021-02-17 15:05:18 +0100731
tierno1d213f42020-04-24 14:02:51 +0000732 if main_topic != "admin" or topic != "tokens":
733 token_info = self.authenticator.authorize(role_permission, _id)
sousaedu80135b92021-02-17 15:05:18 +0100734
tierno1d213f42020-04-24 14:02:51 +0000735 outdata, created_id, done = self.map_operation[role_permission](
sousaedu80135b92021-02-17 15:05:18 +0100736 engine_session, indata, version, _id, _id2, *args, *kwargs
737 )
738
tierno1d213f42020-04-24 14:02:51 +0000739 if created_id:
740 self._set_location_header(main_topic, version, topic, _id)
sousaedu80135b92021-02-17 15:05:18 +0100741
742 cherrypy.response.status = (
743 HTTPStatus.ACCEPTED.value
744 if not done
745 else HTTPStatus.OK.value
746 if outdata is not None
747 else HTTPStatus.NO_CONTENT.value
748 )
749
tierno1d213f42020-04-24 14:02:51 +0000750 return self._format_out(outdata, token_info, _format)
751 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +0100752 if isinstance(
753 e,
754 (
755 RoException,
756 NsException,
757 DbException,
758 FsException,
759 MsgException,
760 AuthException,
761 ValidationError,
762 ),
763 ):
tierno1d213f42020-04-24 14:02:51 +0000764 http_code_value = cherrypy.response.status = e.http_code.value
765 http_code_name = e.http_code.name
766 cherrypy.log("Exception {}".format(e))
767 else:
sousaedu80135b92021-02-17 15:05:18 +0100768 http_code_value = (
769 cherrypy.response.status
770 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
tierno1d213f42020-04-24 14:02:51 +0000771 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
772 http_code_name = HTTPStatus.BAD_REQUEST.name
sousaedu80135b92021-02-17 15:05:18 +0100773
tierno1d213f42020-04-24 14:02:51 +0000774 if hasattr(outdata, "close"): # is an open file
775 outdata.close()
sousaedu80135b92021-02-17 15:05:18 +0100776
tierno1d213f42020-04-24 14:02:51 +0000777 error_text = str(e)
778 rollback.reverse()
sousaedu80135b92021-02-17 15:05:18 +0100779
tierno1d213f42020-04-24 14:02:51 +0000780 for rollback_item in rollback:
781 try:
782 if rollback_item.get("operation") == "set":
sousaedu80135b92021-02-17 15:05:18 +0100783 self.ns.db.set_one(
784 rollback_item["topic"],
785 {"_id": rollback_item["_id"]},
786 rollback_item["content"],
787 fail_on_empty=False,
788 )
tierno1d213f42020-04-24 14:02:51 +0000789 else:
sousaedu80135b92021-02-17 15:05:18 +0100790 self.ns.db.del_one(
791 rollback_item["topic"],
792 {"_id": rollback_item["_id"]},
793 fail_on_empty=False,
794 )
tierno1d213f42020-04-24 14:02:51 +0000795 except Exception as e2:
sousaedu80135b92021-02-17 15:05:18 +0100796 rollback_error_text = "Rollback Exception {}: {}".format(
797 rollback_item, e2
798 )
tierno1d213f42020-04-24 14:02:51 +0000799 cherrypy.log(rollback_error_text)
800 error_text += ". " + rollback_error_text
sousaedu80135b92021-02-17 15:05:18 +0100801
tierno1d213f42020-04-24 14:02:51 +0000802 # if isinstance(e, MsgException):
803 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
804 # engine_topic[:-1], method, error_text)
805 problem_details = {
806 "code": http_code_name,
807 "status": http_code_value,
808 "detail": error_text,
809 }
sousaedu80135b92021-02-17 15:05:18 +0100810
tierno1d213f42020-04-24 14:02:51 +0000811 return self._format_out(problem_details, token_info)
812 # raise cherrypy.HTTPError(e.http_code.value, str(e))
813 finally:
814 if token_info:
815 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
816 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
817 if outdata.get(logging_id):
sousaedu80135b92021-02-17 15:05:18 +0100818 cherrypy.request.login += ";{}={}".format(
819 logging_id, outdata[logging_id][:36]
820 )
tierno1d213f42020-04-24 14:02:51 +0000821
822
823def _start_service():
824 """
825 Callback function called when cherrypy.engine starts
826 Override configuration with env variables
827 Set database, storage, message configuration
828 Init database with admin/admin user password
829 """
tierno70eeb182020-10-19 16:38:00 +0000830 global ro_server, vim_admin_thread
tierno1d213f42020-04-24 14:02:51 +0000831 # global vim_threads
832 cherrypy.log.error("Starting osm_ng_ro")
833 # update general cherrypy configuration
834 update_dict = {}
sousaedu80135b92021-02-17 15:05:18 +0100835 engine_config = cherrypy.tree.apps["/ro"].config
tierno1d213f42020-04-24 14:02:51 +0000836
tierno1d213f42020-04-24 14:02:51 +0000837 for k, v in environ.items():
838 if not k.startswith("OSMRO_"):
839 continue
sousaedu80135b92021-02-17 15:05:18 +0100840
tierno1d213f42020-04-24 14:02:51 +0000841 k1, _, k2 = k[6:].lower().partition("_")
sousaedu80135b92021-02-17 15:05:18 +0100842
tierno1d213f42020-04-24 14:02:51 +0000843 if not k2:
844 continue
sousaedu80135b92021-02-17 15:05:18 +0100845
tierno1d213f42020-04-24 14:02:51 +0000846 try:
847 if k1 in ("server", "test", "auth", "log"):
848 # update [global] configuration
sousaedu80135b92021-02-17 15:05:18 +0100849 update_dict[k1 + "." + k2] = yaml.safe_load(v)
tierno1d213f42020-04-24 14:02:51 +0000850 elif k1 == "static":
851 # update [/static] configuration
852 engine_config["/static"]["tools.staticdir." + k2] = yaml.safe_load(v)
853 elif k1 == "tools":
854 # update [/] configuration
sousaedu80135b92021-02-17 15:05:18 +0100855 engine_config["/"]["tools." + k2.replace("_", ".")] = yaml.safe_load(v)
aticig1ac189e2022-06-30 19:29:04 +0300856 elif k1 in ("message", "database", "storage", "authentication", "period"):
tierno70eeb182020-10-19 16:38:00 +0000857 engine_config[k1][k2] = yaml.safe_load(v)
tierno1d213f42020-04-24 14:02:51 +0000858
859 except Exception as e:
860 raise RoException("Cannot load env '{}': {}".format(k, e))
861
862 if update_dict:
863 cherrypy.config.update(update_dict)
864 engine_config["global"].update(update_dict)
865
866 # logging cherrypy
sousaedu80135b92021-02-17 15:05:18 +0100867 log_format_simple = (
868 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
869 )
870 log_formatter_simple = logging.Formatter(
871 log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S"
872 )
tierno1d213f42020-04-24 14:02:51 +0000873 logger_server = logging.getLogger("cherrypy.error")
874 logger_access = logging.getLogger("cherrypy.access")
875 logger_cherry = logging.getLogger("cherrypy")
sousaedue493e9b2021-02-09 15:30:01 +0100876 logger = logging.getLogger("ro")
tierno1d213f42020-04-24 14:02:51 +0000877
878 if "log.file" in engine_config["global"]:
sousaedu80135b92021-02-17 15:05:18 +0100879 file_handler = logging.handlers.RotatingFileHandler(
880 engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0
881 )
tierno1d213f42020-04-24 14:02:51 +0000882 file_handler.setFormatter(log_formatter_simple)
883 logger_cherry.addHandler(file_handler)
sousaedue493e9b2021-02-09 15:30:01 +0100884 logger.addHandler(file_handler)
sousaedu80135b92021-02-17 15:05:18 +0100885
tierno1d213f42020-04-24 14:02:51 +0000886 # log always to standard output
sousaedu80135b92021-02-17 15:05:18 +0100887 for format_, logger in {
888 "ro.server %(filename)s:%(lineno)s": logger_server,
889 "ro.access %(filename)s:%(lineno)s": logger_access,
890 "%(name)s %(filename)s:%(lineno)s": logger,
891 }.items():
tierno1d213f42020-04-24 14:02:51 +0000892 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
sousaedu80135b92021-02-17 15:05:18 +0100893 log_formatter_cherry = logging.Formatter(
894 log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S"
895 )
tierno1d213f42020-04-24 14:02:51 +0000896 str_handler = logging.StreamHandler()
897 str_handler.setFormatter(log_formatter_cherry)
898 logger.addHandler(str_handler)
899
900 if engine_config["global"].get("log.level"):
901 logger_cherry.setLevel(engine_config["global"]["log.level"])
sousaedue493e9b2021-02-09 15:30:01 +0100902 logger.setLevel(engine_config["global"]["log.level"])
sousaedu80135b92021-02-17 15:05:18 +0100903
tierno1d213f42020-04-24 14:02:51 +0000904 # logging other modules
sousaedu80135b92021-02-17 15:05:18 +0100905 for k1, logname in {
906 "message": "ro.msg",
907 "database": "ro.db",
908 "storage": "ro.fs",
909 }.items():
tierno1d213f42020-04-24 14:02:51 +0000910 engine_config[k1]["logger_name"] = logname
911 logger_module = logging.getLogger(logname)
sousaedu80135b92021-02-17 15:05:18 +0100912
tierno1d213f42020-04-24 14:02:51 +0000913 if "logfile" in engine_config[k1]:
sousaedu80135b92021-02-17 15:05:18 +0100914 file_handler = logging.handlers.RotatingFileHandler(
915 engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0
916 )
tierno1d213f42020-04-24 14:02:51 +0000917 file_handler.setFormatter(log_formatter_simple)
918 logger_module.addHandler(file_handler)
sousaedu80135b92021-02-17 15:05:18 +0100919
tierno1d213f42020-04-24 14:02:51 +0000920 if "loglevel" in engine_config[k1]:
921 logger_module.setLevel(engine_config[k1]["loglevel"])
922 # TODO add more entries, e.g.: storage
923
924 engine_config["assignment"] = {}
925 # ^ 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 +0100926 cherrypy.tree.apps["/ro"].root.ns.start(engine_config)
927 cherrypy.tree.apps["/ro"].root.authenticator.start(engine_config)
928 cherrypy.tree.apps["/ro"].root.ns.init_db(target_version=database_version)
tierno1d213f42020-04-24 14:02:51 +0000929
930 # # start subscriptions thread:
tierno70eeb182020-10-19 16:38:00 +0000931 vim_admin_thread = VimAdminThread(config=engine_config, engine=ro_server.ns)
932 vim_admin_thread.start()
tierno1d213f42020-04-24 14:02:51 +0000933 # # Do not capture except SubscriptionException
934
tierno70eeb182020-10-19 16:38:00 +0000935 # backend = engine_config["authentication"]["backend"]
936 # cherrypy.log.error("Starting OSM NBI Version '{} {}' with '{}' authentication backend"
937 # .format(ro_version, ro_version_date, backend))
tierno1d213f42020-04-24 14:02:51 +0000938
939
940def _stop_service():
941 """
942 Callback function called when cherrypy.engine stops
943 TODO: Ending database connections.
944 """
tierno70eeb182020-10-19 16:38:00 +0000945 global vim_admin_thread
sousaedu80135b92021-02-17 15:05:18 +0100946
tierno70eeb182020-10-19 16:38:00 +0000947 # terminate vim_admin_thread
948 if vim_admin_thread:
949 vim_admin_thread.terminate()
sousaedu80135b92021-02-17 15:05:18 +0100950
tierno70eeb182020-10-19 16:38:00 +0000951 vim_admin_thread = None
sousaedu80135b92021-02-17 15:05:18 +0100952 cherrypy.tree.apps["/ro"].root.ns.stop()
tierno1d213f42020-04-24 14:02:51 +0000953 cherrypy.log.error("Stopping osm_ng_ro")
954
955
956def ro_main(config_file):
957 global ro_server
sousaedu80135b92021-02-17 15:05:18 +0100958
tierno1d213f42020-04-24 14:02:51 +0000959 ro_server = Server()
sousaedu80135b92021-02-17 15:05:18 +0100960 cherrypy.engine.subscribe("start", _start_service)
961 cherrypy.engine.subscribe("stop", _stop_service)
962 cherrypy.quickstart(ro_server, "/ro", config_file)
tierno1d213f42020-04-24 14:02:51 +0000963
964
965def usage():
sousaedu80135b92021-02-17 15:05:18 +0100966 print(
967 """Usage: {} [options]
tierno1d213f42020-04-24 14:02:51 +0000968 -c|--config [configuration_file]: loads the configuration file (default: ./ro.cfg)
969 -h|--help: shows this help
sousaedu80135b92021-02-17 15:05:18 +0100970 """.format(
971 sys.argv[0]
972 )
973 )
tierno1d213f42020-04-24 14:02:51 +0000974 # --log-socket-host HOST: send logs to this host")
975 # --log-socket-port PORT: send logs using this port (default: 9022)")
976
977
sousaedu80135b92021-02-17 15:05:18 +0100978if __name__ == "__main__":
tierno1d213f42020-04-24 14:02:51 +0000979 try:
980 # load parameters and configuration
981 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
982 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
983 config_file = None
sousaedu80135b92021-02-17 15:05:18 +0100984
tierno1d213f42020-04-24 14:02:51 +0000985 for o, a in opts:
986 if o in ("-h", "--help"):
987 usage()
988 sys.exit()
989 elif o in ("-c", "--config"):
990 config_file = a
991 else:
aticig7b521f72022-07-15 00:43:09 +0300992 raise ValueError("Unhandled option")
sousaedu80135b92021-02-17 15:05:18 +0100993
tierno1d213f42020-04-24 14:02:51 +0000994 if config_file:
995 if not path.isfile(config_file):
sousaedu80135b92021-02-17 15:05:18 +0100996 print(
997 "configuration file '{}' that not exist".format(config_file),
998 file=sys.stderr,
999 )
tierno1d213f42020-04-24 14:02:51 +00001000 exit(1)
1001 else:
sousaedu80135b92021-02-17 15:05:18 +01001002 for config_file in (
1003 path.dirname(__file__) + "/ro.cfg",
1004 "./ro.cfg",
1005 "/etc/osm/ro.cfg",
1006 ):
tierno1d213f42020-04-24 14:02:51 +00001007 if path.isfile(config_file):
1008 break
1009 else:
sousaedu80135b92021-02-17 15:05:18 +01001010 print(
1011 "No configuration file 'ro.cfg' found neither at local folder nor at /etc/osm/",
1012 file=sys.stderr,
1013 )
tierno1d213f42020-04-24 14:02:51 +00001014 exit(1)
sousaedu80135b92021-02-17 15:05:18 +01001015
tierno1d213f42020-04-24 14:02:51 +00001016 ro_main(config_file)
tierno70eeb182020-10-19 16:38:00 +00001017 except KeyboardInterrupt:
1018 print("KeyboardInterrupt. Finishing", file=sys.stderr)
tierno1d213f42020-04-24 14:02:51 +00001019 except getopt.GetoptError as e:
1020 print(str(e), file=sys.stderr)
1021 # usage()
1022 exit(1)