blob: 51c22bf5e40a1d854d63c7d1378a23a149730375 [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
63
64"""
65
66valid_query_string = ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
67# ^ Contains possible administrative query string words:
68# ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
69# (not owned by my session project).
70# PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
71# FORCE=True(by default)|False: Force edition/deletion operations
72# SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
73
74valid_url_methods = {
75 # contains allowed URL and methods, and the role_permission name
76 "admin": {
77 "v1": {
78 "tokens": {
79 "METHODS": ("POST",),
80 "ROLE_PERMISSION": "tokens:",
sousaedu80135b92021-02-17 15:05:18 +010081 "<ID>": {"METHODS": ("DELETE",), "ROLE_PERMISSION": "tokens:id:"},
tierno1d213f42020-04-24 14:02:51 +000082 },
83 }
84 },
85 "ns": {
86 "v1": {
k4.rahul78f474e2022-05-02 15:47:57 +000087 "rebuild": {
88 "METHODS": ("POST",),
89 "ROLE_PERMISSION": "rebuild:",
90 "<ID>": {
91 "METHODS": ("POST",),
92 "ROLE_PERMISSION": "rebuild:id:",
93 },
94 },
95 "start": {
96 "METHODS": ("POST",),
97 "ROLE_PERMISSION": "start:",
98 "<ID>": {
99 "METHODS": ("POST",),
100 "ROLE_PERMISSION": "start:id:",
101 },
102 },
103 "stop": {
104 "METHODS": ("POST",),
105 "ROLE_PERMISSION": "stop:",
106 "<ID>": {
107 "METHODS": ("POST",),
108 "ROLE_PERMISSION": "stop:id:",
109 },
110 },
tierno1d213f42020-04-24 14:02:51 +0000111 "deploy": {
112 "METHODS": ("GET",),
113 "ROLE_PERMISSION": "deploy:",
114 "<ID>": {
115 "METHODS": ("GET", "POST", "DELETE"),
116 "ROLE_PERMISSION": "deploy:id:",
117 "<ID>": {
118 "METHODS": ("GET",),
119 "ROLE_PERMISSION": "deploy:id:id:",
120 "cancel": {
121 "METHODS": ("POST",),
122 "ROLE_PERMISSION": "deploy:id:id:cancel",
sousaedu80135b92021-02-17 15:05:18 +0100123 },
124 },
125 },
tierno1d213f42020-04-24 14:02:51 +0000126 },
palaciosj8f2060b2022-02-24 12:05:59 +0000127 "recreate": {
128 "<ID>": {
129 "METHODS": ("POST"),
130 "ROLE_PERMISSION": "recreate:id:",
131 "<ID>": {
132 "METHODS": ("GET",),
133 "ROLE_PERMISSION": "recreate:id:id:",
134 },
135 },
136 },
elumalai8658c2c2022-04-28 19:09:31 +0530137 "migrate": {
138 "<ID>": {
139 "METHODS": ("POST"),
140 "ROLE_PERMISSION": "migrate:id:",
141 "<ID>": {
142 "METHODS": ("GET",),
143 "ROLE_PERMISSION": "migrate:id:id:",
144 },
145 },
146 },
sritharan29a4c1a2022-05-05 12:15:04 +0000147 "verticalscale": {
148 "<ID>": {
149 "METHODS": ("POST"),
150 "ROLE_PERMISSION": "verticalscale:id:",
151 "<ID>": {
152 "METHODS": ("GET",),
153 "ROLE_PERMISSION": "verticalscale:id:id:",
154 },
155 },
156 },
tierno1d213f42020-04-24 14:02:51 +0000157 }
158 },
159}
160
161
162class RoException(Exception):
tierno1d213f42020-04-24 14:02:51 +0000163 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
164 Exception.__init__(self, message)
165 self.http_code = http_code
166
167
168class AuthException(RoException):
169 pass
170
171
172class Authenticator:
tierno1d213f42020-04-24 14:02:51 +0000173 def __init__(self, valid_url_methods, valid_query_string):
174 self.valid_url_methods = valid_url_methods
175 self.valid_query_string = valid_query_string
176
177 def authorize(self, *args, **kwargs):
178 return {"token": "ok", "id": "ok"}
sousaedu80135b92021-02-17 15:05:18 +0100179
tierno1d213f42020-04-24 14:02:51 +0000180 def new_token(self, token_info, indata, remote):
sousaedu80135b92021-02-17 15:05:18 +0100181 return {"token": "ok", "id": "ok", "remote": remote}
tierno1d213f42020-04-24 14:02:51 +0000182
183 def del_token(self, token_id):
184 pass
185
186 def start(self, engine_config):
187 pass
188
189
190class Server(object):
191 instance = 0
192 # to decode bytes to str
193 reader = getreader("utf-8")
194
195 def __init__(self):
196 self.instance += 1
197 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
198 self.ns = Ns()
199 self.map_operation = {
200 "token:post": self.new_token,
201 "token:id:delete": self.del_token,
202 "deploy:get": self.ns.get_deploy,
203 "deploy:id:get": self.ns.get_actions,
204 "deploy:id:post": self.ns.deploy,
205 "deploy:id:delete": self.ns.delete,
206 "deploy:id:id:get": self.ns.status,
207 "deploy:id:id:cancel:post": self.ns.cancel,
k4.rahul78f474e2022-05-02 15:47:57 +0000208 "rebuild:id:post": self.ns.rebuild_start_stop,
209 "start:id:post": self.ns.rebuild_start_stop,
210 "stop:id:post": self.ns.rebuild_start_stop,
palaciosj8f2060b2022-02-24 12:05:59 +0000211 "recreate:id:post": self.ns.recreate,
212 "recreate:id:id:get": self.ns.recreate_status,
elumalai8658c2c2022-04-28 19:09:31 +0530213 "migrate:id:post": self.ns.migrate,
sritharan29a4c1a2022-05-05 12:15:04 +0000214 "verticalscale:id:post": self.ns.verticalscale,
tierno1d213f42020-04-24 14:02:51 +0000215 }
216
217 def _format_in(self, kwargs):
Gulsum Atici2f995052022-11-23 16:06:40 +0300218 error_text = ""
tierno1d213f42020-04-24 14:02:51 +0000219 try:
220 indata = None
sousaedu80135b92021-02-17 15:05:18 +0100221
tierno1d213f42020-04-24 14:02:51 +0000222 if cherrypy.request.body.length:
223 error_text = "Invalid input format "
224
225 if "Content-Type" in cherrypy.request.headers:
226 if "application/json" in cherrypy.request.headers["Content-Type"]:
227 error_text = "Invalid json format "
228 indata = json.load(self.reader(cherrypy.request.body))
229 cherrypy.request.headers.pop("Content-File-MD5", None)
230 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
231 error_text = "Invalid yaml format "
aticig7b521f72022-07-15 00:43:09 +0300232 indata = yaml.safe_load(cherrypy.request.body)
tierno1d213f42020-04-24 14:02:51 +0000233 cherrypy.request.headers.pop("Content-File-MD5", None)
sousaedu80135b92021-02-17 15:05:18 +0100234 elif (
235 "application/binary" in cherrypy.request.headers["Content-Type"]
236 or "application/gzip"
237 in cherrypy.request.headers["Content-Type"]
238 or "application/zip" in cherrypy.request.headers["Content-Type"]
239 or "text/plain" in cherrypy.request.headers["Content-Type"]
240 ):
tierno1d213f42020-04-24 14:02:51 +0000241 indata = cherrypy.request.body # .read()
sousaedu80135b92021-02-17 15:05:18 +0100242 elif (
243 "multipart/form-data"
244 in cherrypy.request.headers["Content-Type"]
245 ):
tierno1d213f42020-04-24 14:02:51 +0000246 if "descriptor_file" in kwargs:
247 filecontent = kwargs.pop("descriptor_file")
sousaedu80135b92021-02-17 15:05:18 +0100248
tierno1d213f42020-04-24 14:02:51 +0000249 if not filecontent.file:
sousaedu80135b92021-02-17 15:05:18 +0100250 raise RoException(
251 "empty file or content", HTTPStatus.BAD_REQUEST
252 )
253
tierno1d213f42020-04-24 14:02:51 +0000254 indata = filecontent.file # .read()
sousaedu80135b92021-02-17 15:05:18 +0100255
tierno1d213f42020-04-24 14:02:51 +0000256 if filecontent.content_type.value:
garciadeblasaca8cb52023-12-21 16:28:15 +0100257 cherrypy.request.headers["Content-Type"] = (
258 filecontent.content_type.value
259 )
tierno1d213f42020-04-24 14:02:51 +0000260 else:
261 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
262 # "Only 'Content-Type' of type 'application/json' or
263 # 'application/yaml' for input format are available")
264 error_text = "Invalid yaml format "
aticig7b521f72022-07-15 00:43:09 +0300265 indata = yaml.safe_load(cherrypy.request.body)
tierno1d213f42020-04-24 14:02:51 +0000266 cherrypy.request.headers.pop("Content-File-MD5", None)
267 else:
268 error_text = "Invalid yaml format "
aticig7b521f72022-07-15 00:43:09 +0300269 indata = yaml.safe_load(cherrypy.request.body)
tierno1d213f42020-04-24 14:02:51 +0000270 cherrypy.request.headers.pop("Content-File-MD5", None)
sousaedu80135b92021-02-17 15:05:18 +0100271
tierno1d213f42020-04-24 14:02:51 +0000272 if not indata:
273 indata = {}
274
275 format_yaml = False
276 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
277 format_yaml = True
278
279 for k, v in kwargs.items():
280 if isinstance(v, str):
281 if v == "":
282 kwargs[k] = None
283 elif format_yaml:
284 try:
aticig7b521f72022-07-15 00:43:09 +0300285 kwargs[k] = yaml.safe_load(v)
286 except Exception as yaml_error:
287 logging.exception(
288 f"{yaml_error} occured while parsing the yaml"
289 )
sousaedu80135b92021-02-17 15:05:18 +0100290 elif (
291 k.endswith(".gt")
292 or k.endswith(".lt")
293 or k.endswith(".gte")
294 or k.endswith(".lte")
295 ):
tierno1d213f42020-04-24 14:02:51 +0000296 try:
297 kwargs[k] = int(v)
298 except Exception:
299 try:
300 kwargs[k] = float(v)
aticig7b521f72022-07-15 00:43:09 +0300301 except Exception as keyword_error:
302 logging.exception(
303 f"{keyword_error} occured while getting the keyword arguments"
304 )
tierno1d213f42020-04-24 14:02:51 +0000305 elif v.find(",") > 0:
306 kwargs[k] = v.split(",")
307 elif isinstance(v, (list, tuple)):
308 for index in range(0, len(v)):
309 if v[index] == "":
310 v[index] = None
311 elif format_yaml:
312 try:
aticig7b521f72022-07-15 00:43:09 +0300313 v[index] = yaml.safe_load(v[index])
314 except Exception as error:
315 logging.exception(
316 f"{error} occured while parsing the yaml"
317 )
tierno1d213f42020-04-24 14:02:51 +0000318
319 return indata
320 except (ValueError, yaml.YAMLError) as exc:
321 raise RoException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
322 except KeyError as exc:
323 raise RoException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
324 except Exception as exc:
325 raise RoException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
326
327 @staticmethod
328 def _format_out(data, token_info=None, _format=None):
329 """
330 return string of dictionary data according to requested json, yaml, xml. By default json
331 :param data: response to be sent. Can be a dict, text or file
332 :param token_info: Contains among other username and project
333 :param _format: The format to be set as Content-Type if data is a file
334 :return: None
335 """
336 accept = cherrypy.request.headers.get("Accept")
sousaedu80135b92021-02-17 15:05:18 +0100337
tierno1d213f42020-04-24 14:02:51 +0000338 if data is None:
339 if accept and "text/html" in accept:
sousaedu80135b92021-02-17 15:05:18 +0100340 return html.format(
341 data, cherrypy.request, cherrypy.response, token_info
342 )
343
tierno1d213f42020-04-24 14:02:51 +0000344 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
345 return
346 elif hasattr(data, "read"): # file object
347 if _format:
348 cherrypy.response.headers["Content-Type"] = _format
349 elif "b" in data.mode: # binariy asssumig zip
sousaedu80135b92021-02-17 15:05:18 +0100350 cherrypy.response.headers["Content-Type"] = "application/zip"
tierno1d213f42020-04-24 14:02:51 +0000351 else:
sousaedu80135b92021-02-17 15:05:18 +0100352 cherrypy.response.headers["Content-Type"] = "text/plain"
353
tierno1d213f42020-04-24 14:02:51 +0000354 # TODO check that cherrypy close file. If not implement pending things to close per thread next
355 return data
sousaedu80135b92021-02-17 15:05:18 +0100356
tierno1d213f42020-04-24 14:02:51 +0000357 if accept:
358 if "application/json" in accept:
garciadeblasaca8cb52023-12-21 16:28:15 +0100359 cherrypy.response.headers["Content-Type"] = (
360 "application/json; charset=utf-8"
361 )
tierno1d213f42020-04-24 14:02:51 +0000362 a = json.dumps(data, indent=4) + "\n"
sousaedu80135b92021-02-17 15:05:18 +0100363
tierno1d213f42020-04-24 14:02:51 +0000364 return a.encode("utf8")
365 elif "text/html" in accept:
sousaedu80135b92021-02-17 15:05:18 +0100366 return html.format(
367 data, cherrypy.request, cherrypy.response, token_info
368 )
369 elif (
370 "application/yaml" in accept
371 or "*/*" in accept
372 or "text/plain" in accept
373 ):
tierno1d213f42020-04-24 14:02:51 +0000374 pass
375 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
376 elif cherrypy.response.status >= 400:
sousaedu80135b92021-02-17 15:05:18 +0100377 raise cherrypy.HTTPError(
378 HTTPStatus.NOT_ACCEPTABLE.value,
379 "Only 'Accept' of type 'application/json' or 'application/yaml' "
380 "for output format are available",
381 )
382
383 cherrypy.response.headers["Content-Type"] = "application/yaml"
384
385 return yaml.safe_dump(
386 data,
387 explicit_start=True,
388 indent=4,
389 default_flow_style=False,
390 tags=False,
391 encoding="utf-8",
392 allow_unicode=True,
393 ) # , canonical=True, default_style='"'
tierno1d213f42020-04-24 14:02:51 +0000394
395 @cherrypy.expose
396 def index(self, *args, **kwargs):
397 token_info = None
sousaedu80135b92021-02-17 15:05:18 +0100398
tierno1d213f42020-04-24 14:02:51 +0000399 try:
400 if cherrypy.request.method == "GET":
401 token_info = self.authenticator.authorize()
sousaedu80135b92021-02-17 15:05:18 +0100402 outdata = token_info # Home page
tierno1d213f42020-04-24 14:02:51 +0000403 else:
sousaedu80135b92021-02-17 15:05:18 +0100404 raise cherrypy.HTTPError(
405 HTTPStatus.METHOD_NOT_ALLOWED.value,
406 "Method {} not allowed for tokens".format(cherrypy.request.method),
407 )
tierno1d213f42020-04-24 14:02:51 +0000408
409 return self._format_out(outdata, token_info)
tierno1d213f42020-04-24 14:02:51 +0000410 except (NsException, AuthException) as e:
411 # cherrypy.log("index Exception {}".format(e))
412 cherrypy.response.status = e.http_code.value
sousaedu80135b92021-02-17 15:05:18 +0100413
tierno1d213f42020-04-24 14:02:51 +0000414 return self._format_out("Welcome to OSM!", token_info)
415
416 @cherrypy.expose
417 def version(self, *args, **kwargs):
418 # TODO consider to remove and provide version using the static version file
419 try:
420 if cherrypy.request.method != "GET":
sousaedu80135b92021-02-17 15:05:18 +0100421 raise RoException(
422 "Only method GET is allowed",
423 HTTPStatus.METHOD_NOT_ALLOWED,
424 )
tierno1d213f42020-04-24 14:02:51 +0000425 elif args or kwargs:
sousaedu80135b92021-02-17 15:05:18 +0100426 raise RoException(
427 "Invalid URL or query string for version",
428 HTTPStatus.METHOD_NOT_ALLOWED,
429 )
430
tierno1d213f42020-04-24 14:02:51 +0000431 # TODO include version of other modules, pick up from some kafka admin message
432 osm_ng_ro_version = {"version": ro_version, "date": ro_version_date}
sousaedu80135b92021-02-17 15:05:18 +0100433
tierno1d213f42020-04-24 14:02:51 +0000434 return self._format_out(osm_ng_ro_version)
435 except RoException as e:
436 cherrypy.response.status = e.http_code.value
437 problem_details = {
438 "code": e.http_code.name,
439 "status": e.http_code.value,
440 "detail": str(e),
441 }
sousaedu80135b92021-02-17 15:05:18 +0100442
tierno1d213f42020-04-24 14:02:51 +0000443 return self._format_out(problem_details, None)
444
445 def new_token(self, engine_session, indata, *args, **kwargs):
446 token_info = None
447
448 try:
449 token_info = self.authenticator.authorize()
450 except Exception:
451 token_info = None
sousaedu80135b92021-02-17 15:05:18 +0100452
tierno1d213f42020-04-24 14:02:51 +0000453 if kwargs:
454 indata.update(kwargs)
sousaedu80135b92021-02-17 15:05:18 +0100455
tierno1d213f42020-04-24 14:02:51 +0000456 # This is needed to log the user when authentication fails
457 cherrypy.request.login = "{}".format(indata.get("username", "-"))
sousaedu80135b92021-02-17 15:05:18 +0100458 token_info = self.authenticator.new_token(
459 token_info, indata, cherrypy.request.remote
460 )
461 cherrypy.session["Authorization"] = token_info["id"]
tierno1d213f42020-04-24 14:02:51 +0000462 self._set_location_header("admin", "v1", "tokens", token_info["id"])
463 # for logging
464
465 # cherrypy.response.cookie["Authorization"] = outdata["id"]
466 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
sousaedu80135b92021-02-17 15:05:18 +0100467
tierno1d213f42020-04-24 14:02:51 +0000468 return token_info, token_info["id"], True
469
470 def del_token(self, engine_session, indata, version, _id, *args, **kwargs):
471 token_id = _id
sousaedu80135b92021-02-17 15:05:18 +0100472
tierno1d213f42020-04-24 14:02:51 +0000473 if not token_id and "id" in kwargs:
474 token_id = kwargs["id"]
475 elif not token_id:
476 token_info = self.authenticator.authorize()
477 # for logging
478 token_id = token_info["id"]
sousaedu80135b92021-02-17 15:05:18 +0100479
tierno1d213f42020-04-24 14:02:51 +0000480 self.authenticator.del_token(token_id)
481 token_info = None
sousaedu80135b92021-02-17 15:05:18 +0100482 cherrypy.session["Authorization"] = "logout"
tierno1d213f42020-04-24 14:02:51 +0000483 # cherrypy.response.cookie["Authorization"] = token_id
484 # cherrypy.response.cookie["Authorization"]['expires'] = 0
sousaedu80135b92021-02-17 15:05:18 +0100485
tierno1d213f42020-04-24 14:02:51 +0000486 return None, None, True
sousaedu80135b92021-02-17 15:05:18 +0100487
tierno1d213f42020-04-24 14:02:51 +0000488 @cherrypy.expose
489 def test(self, *args, **kwargs):
sousaedu80135b92021-02-17 15:05:18 +0100490 if not cherrypy.config.get("server.enable_test") or (
491 isinstance(cherrypy.config["server.enable_test"], str)
492 and cherrypy.config["server.enable_test"].lower() == "false"
493 ):
tierno1d213f42020-04-24 14:02:51 +0000494 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value
tierno1d213f42020-04-24 14:02:51 +0000495
sousaedu80135b92021-02-17 15:05:18 +0100496 return "test URL is disabled"
497
498 thread_info = None
499
500 if args and args[0] == "help":
501 return (
502 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
503 "sleep/<time>\nmessage/topic\n</pre></html>"
504 )
tierno1d213f42020-04-24 14:02:51 +0000505 elif args and args[0] == "init":
506 try:
507 # self.ns.load_dbase(cherrypy.request.app.config)
508 self.ns.create_admin()
sousaedu80135b92021-02-17 15:05:18 +0100509
tierno1d213f42020-04-24 14:02:51 +0000510 return "Done. User 'admin', password 'admin' created"
511 except Exception:
512 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
sousaedu80135b92021-02-17 15:05:18 +0100513
tierno1d213f42020-04-24 14:02:51 +0000514 return self._format_out("Database already initialized")
515 elif args and args[0] == "file":
sousaedu80135b92021-02-17 15:05:18 +0100516 return cherrypy.lib.static.serve_file(
517 cherrypy.tree.apps["/ro"].config["storage"]["path"] + "/" + args[1],
518 "text/plain",
519 "attachment",
520 )
tierno1d213f42020-04-24 14:02:51 +0000521 elif args and args[0] == "file2":
sousaedu80135b92021-02-17 15:05:18 +0100522 f_path = cherrypy.tree.apps["/ro"].config["storage"]["path"] + "/" + args[1]
tierno1d213f42020-04-24 14:02:51 +0000523 f = open(f_path, "r")
524 cherrypy.response.headers["Content-type"] = "text/plain"
525 return f
526
527 elif len(args) == 2 and args[0] == "db-clear":
528 deleted_info = self.ns.db.del_list(args[1], kwargs)
529 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
530 elif len(args) and args[0] == "fs-clear":
531 if len(args) >= 2:
532 folders = (args[1],)
533 else:
534 folders = self.ns.fs.dir_ls(".")
sousaedu80135b92021-02-17 15:05:18 +0100535
tierno1d213f42020-04-24 14:02:51 +0000536 for folder in folders:
537 self.ns.fs.file_delete(folder)
sousaedu80135b92021-02-17 15:05:18 +0100538
tierno1d213f42020-04-24 14:02:51 +0000539 return ",".join(folders) + " folders deleted\n"
540 elif args and args[0] == "login":
541 if not cherrypy.request.headers.get("Authorization"):
garciadeblasaca8cb52023-12-21 16:28:15 +0100542 cherrypy.response.headers["WWW-Authenticate"] = (
543 'Basic realm="Access to OSM site", charset="UTF-8"'
544 )
tierno1d213f42020-04-24 14:02:51 +0000545 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
546 elif args and args[0] == "login2":
547 if not cherrypy.request.headers.get("Authorization"):
garciadeblasaca8cb52023-12-21 16:28:15 +0100548 cherrypy.response.headers["WWW-Authenticate"] = (
549 'Bearer realm="Access to OSM site"'
550 )
tierno1d213f42020-04-24 14:02:51 +0000551 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
552 elif args and args[0] == "sleep":
553 sleep_time = 5
sousaedu80135b92021-02-17 15:05:18 +0100554
tierno1d213f42020-04-24 14:02:51 +0000555 try:
556 sleep_time = int(args[1])
557 except Exception:
558 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
559 return self._format_out("Database already initialized")
sousaedu80135b92021-02-17 15:05:18 +0100560
tierno1d213f42020-04-24 14:02:51 +0000561 thread_info = cherrypy.thread_data
562 print(thread_info)
563 time.sleep(sleep_time)
564 # thread_info
565 elif len(args) >= 2 and args[0] == "message":
566 main_topic = args[1]
567 return_text = "<html><pre>{} ->\n".format(main_topic)
sousaedu80135b92021-02-17 15:05:18 +0100568
tierno1d213f42020-04-24 14:02:51 +0000569 try:
sousaedu80135b92021-02-17 15:05:18 +0100570 if cherrypy.request.method == "POST":
aticig7b521f72022-07-15 00:43:09 +0300571 to_send = yaml.safe_load(cherrypy.request.body)
tierno1d213f42020-04-24 14:02:51 +0000572 for k, v in to_send.items():
573 self.ns.msg.write(main_topic, k, v)
574 return_text += " {}: {}\n".format(k, v)
sousaedu80135b92021-02-17 15:05:18 +0100575 elif cherrypy.request.method == "GET":
tierno1d213f42020-04-24 14:02:51 +0000576 for k, v in kwargs.items():
aticig7b521f72022-07-15 00:43:09 +0300577 self.ns.msg.write(main_topic, k, yaml.safe_load(v))
578 return_text += " {}: {}\n".format(k, yaml.safe_load(v))
tierno1d213f42020-04-24 14:02:51 +0000579 except Exception as e:
580 return_text += "Error: " + str(e)
sousaedu80135b92021-02-17 15:05:18 +0100581
tierno1d213f42020-04-24 14:02:51 +0000582 return_text += "</pre></html>\n"
sousaedu80135b92021-02-17 15:05:18 +0100583
tierno1d213f42020-04-24 14:02:51 +0000584 return return_text
585
586 return_text = (
sousaedu80135b92021-02-17 15:05:18 +0100587 "<html><pre>\nheaders:\n args: {}\n".format(args)
588 + " kwargs: {}\n".format(kwargs)
589 + " headers: {}\n".format(cherrypy.request.headers)
590 + " path_info: {}\n".format(cherrypy.request.path_info)
591 + " query_string: {}\n".format(cherrypy.request.query_string)
592 + " session: {}\n".format(cherrypy.session)
593 + " cookie: {}\n".format(cherrypy.request.cookie)
594 + " method: {}\n".format(cherrypy.request.method)
595 + " session: {}\n".format(cherrypy.session.get("fieldname"))
596 + " body:\n"
597 )
tierno1d213f42020-04-24 14:02:51 +0000598 return_text += " length: {}\n".format(cherrypy.request.body.length)
sousaedu80135b92021-02-17 15:05:18 +0100599
tierno1d213f42020-04-24 14:02:51 +0000600 if cherrypy.request.body.length:
601 return_text += " content: {}\n".format(
sousaedu80135b92021-02-17 15:05:18 +0100602 str(
603 cherrypy.request.body.read(
604 int(cherrypy.request.headers.get("Content-Length", 0))
605 )
606 )
607 )
608
tierno1d213f42020-04-24 14:02:51 +0000609 if thread_info:
610 return_text += "thread: {}\n".format(thread_info)
sousaedu80135b92021-02-17 15:05:18 +0100611
tierno1d213f42020-04-24 14:02:51 +0000612 return_text += "</pre></html>"
sousaedu80135b92021-02-17 15:05:18 +0100613
tierno1d213f42020-04-24 14:02:51 +0000614 return return_text
615
616 @staticmethod
617 def _check_valid_url_method(method, *args):
618 if len(args) < 3:
sousaedu80135b92021-02-17 15:05:18 +0100619 raise RoException(
620 "URL must contain at least 'main_topic/version/topic'",
621 HTTPStatus.METHOD_NOT_ALLOWED,
622 )
tierno1d213f42020-04-24 14:02:51 +0000623
624 reference = valid_url_methods
625 for arg in args:
626 if arg is None:
627 break
sousaedu80135b92021-02-17 15:05:18 +0100628
tierno1d213f42020-04-24 14:02:51 +0000629 if not isinstance(reference, dict):
sousaedu80135b92021-02-17 15:05:18 +0100630 raise RoException(
631 "URL contains unexpected extra items '{}'".format(arg),
632 HTTPStatus.METHOD_NOT_ALLOWED,
633 )
tierno1d213f42020-04-24 14:02:51 +0000634
635 if arg in reference:
636 reference = reference[arg]
637 elif "<ID>" in reference:
638 reference = reference["<ID>"]
639 elif "*" in reference:
640 # reference = reference["*"]
641 break
642 else:
sousaedu80135b92021-02-17 15:05:18 +0100643 raise RoException(
644 "Unexpected URL item {}".format(arg),
645 HTTPStatus.METHOD_NOT_ALLOWED,
646 )
647
tierno1d213f42020-04-24 14:02:51 +0000648 if "TODO" in reference and method in reference["TODO"]:
sousaedu80135b92021-02-17 15:05:18 +0100649 raise RoException(
650 "Method {} not supported yet for this URL".format(method),
651 HTTPStatus.NOT_IMPLEMENTED,
652 )
tierno1d213f42020-04-24 14:02:51 +0000653 elif "METHODS" not in reference or method not in reference["METHODS"]:
sousaedu80135b92021-02-17 15:05:18 +0100654 raise RoException(
655 "Method {} not supported for this URL".format(method),
656 HTTPStatus.METHOD_NOT_ALLOWED,
657 )
658
tierno1d213f42020-04-24 14:02:51 +0000659 return reference["ROLE_PERMISSION"] + method.lower()
660
661 @staticmethod
662 def _set_location_header(main_topic, version, topic, id):
663 """
664 Insert response header Location with the URL of created item base on URL params
665 :param main_topic:
666 :param version:
667 :param topic:
668 :param id:
669 :return: None
670 """
671 # 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 +0100672 cherrypy.response.headers["Location"] = "/ro/{}/{}/{}/{}".format(
673 main_topic, version, topic, id
674 )
675
tierno1d213f42020-04-24 14:02:51 +0000676 return
677
678 @cherrypy.expose
sousaedu80135b92021-02-17 15:05:18 +0100679 def default(
680 self,
681 main_topic=None,
682 version=None,
683 topic=None,
684 _id=None,
685 _id2=None,
686 *args,
687 **kwargs,
688 ):
tierno1d213f42020-04-24 14:02:51 +0000689 token_info = None
elumalai321d2e92023-04-28 17:03:07 +0530690 outdata = {}
tierno1d213f42020-04-24 14:02:51 +0000691 _format = None
692 method = "DONE"
693 rollback = []
694 engine_session = None
sousaedu80135b92021-02-17 15:05:18 +0100695
tierno1d213f42020-04-24 14:02:51 +0000696 try:
697 if not main_topic or not version or not topic:
sousaedu80135b92021-02-17 15:05:18 +0100698 raise RoException(
699 "URL must contain at least 'main_topic/version/topic'",
700 HTTPStatus.METHOD_NOT_ALLOWED,
701 )
tierno1d213f42020-04-24 14:02:51 +0000702
sousaedu80135b92021-02-17 15:05:18 +0100703 if main_topic not in (
704 "admin",
705 "ns",
706 ):
707 raise RoException(
708 "URL main_topic '{}' not supported".format(main_topic),
709 HTTPStatus.METHOD_NOT_ALLOWED,
710 )
711
712 if version != "v1":
713 raise RoException(
714 "URL version '{}' not supported".format(version),
715 HTTPStatus.METHOD_NOT_ALLOWED,
716 )
717
718 if (
719 kwargs
720 and "METHOD" in kwargs
721 and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
722 ):
tierno1d213f42020-04-24 14:02:51 +0000723 method = kwargs.pop("METHOD")
724 else:
725 method = cherrypy.request.method
726
sousaedu80135b92021-02-17 15:05:18 +0100727 role_permission = self._check_valid_url_method(
728 method, main_topic, version, topic, _id, _id2, *args, **kwargs
729 )
tierno1d213f42020-04-24 14:02:51 +0000730 # skip token validation if requesting a token
731 indata = self._format_in(kwargs)
sousaedu80135b92021-02-17 15:05:18 +0100732
tierno1d213f42020-04-24 14:02:51 +0000733 if main_topic != "admin" or topic != "tokens":
734 token_info = self.authenticator.authorize(role_permission, _id)
sousaedu80135b92021-02-17 15:05:18 +0100735
tierno1d213f42020-04-24 14:02:51 +0000736 outdata, created_id, done = self.map_operation[role_permission](
sousaedu80135b92021-02-17 15:05:18 +0100737 engine_session, indata, version, _id, _id2, *args, *kwargs
738 )
739
tierno1d213f42020-04-24 14:02:51 +0000740 if created_id:
741 self._set_location_header(main_topic, version, topic, _id)
sousaedu80135b92021-02-17 15:05:18 +0100742
743 cherrypy.response.status = (
744 HTTPStatus.ACCEPTED.value
745 if not done
garciadeblasaca8cb52023-12-21 16:28:15 +0100746 else (
747 HTTPStatus.OK.value
748 if outdata is not None
749 else HTTPStatus.NO_CONTENT.value
750 )
sousaedu80135b92021-02-17 15:05:18 +0100751 )
752
tierno1d213f42020-04-24 14:02:51 +0000753 return self._format_out(outdata, token_info, _format)
754 except Exception as e:
sousaedu80135b92021-02-17 15:05:18 +0100755 if isinstance(
756 e,
757 (
758 RoException,
759 NsException,
760 DbException,
761 FsException,
762 MsgException,
763 AuthException,
764 ValidationError,
765 ),
766 ):
tierno1d213f42020-04-24 14:02:51 +0000767 http_code_value = cherrypy.response.status = e.http_code.value
768 http_code_name = e.http_code.name
769 cherrypy.log("Exception {}".format(e))
770 else:
garciadeblasaca8cb52023-12-21 16:28:15 +0100771 http_code_value = cherrypy.response.status = (
772 HTTPStatus.BAD_REQUEST.value
773 ) # INTERNAL_SERVER_ERROR
tierno1d213f42020-04-24 14:02:51 +0000774 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
775 http_code_name = HTTPStatus.BAD_REQUEST.name
sousaedu80135b92021-02-17 15:05:18 +0100776
tierno1d213f42020-04-24 14:02:51 +0000777 if hasattr(outdata, "close"): # is an open file
778 outdata.close()
sousaedu80135b92021-02-17 15:05:18 +0100779
tierno1d213f42020-04-24 14:02:51 +0000780 error_text = str(e)
781 rollback.reverse()
sousaedu80135b92021-02-17 15:05:18 +0100782
tierno1d213f42020-04-24 14:02:51 +0000783 for rollback_item in rollback:
784 try:
785 if rollback_item.get("operation") == "set":
sousaedu80135b92021-02-17 15:05:18 +0100786 self.ns.db.set_one(
787 rollback_item["topic"],
788 {"_id": rollback_item["_id"]},
789 rollback_item["content"],
790 fail_on_empty=False,
791 )
tierno1d213f42020-04-24 14:02:51 +0000792 else:
sousaedu80135b92021-02-17 15:05:18 +0100793 self.ns.db.del_one(
794 rollback_item["topic"],
795 {"_id": rollback_item["_id"]},
796 fail_on_empty=False,
797 )
tierno1d213f42020-04-24 14:02:51 +0000798 except Exception as e2:
sousaedu80135b92021-02-17 15:05:18 +0100799 rollback_error_text = "Rollback Exception {}: {}".format(
800 rollback_item, e2
801 )
tierno1d213f42020-04-24 14:02:51 +0000802 cherrypy.log(rollback_error_text)
803 error_text += ". " + rollback_error_text
sousaedu80135b92021-02-17 15:05:18 +0100804
tierno1d213f42020-04-24 14:02:51 +0000805 # if isinstance(e, MsgException):
806 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
807 # engine_topic[:-1], method, error_text)
808 problem_details = {
809 "code": http_code_name,
810 "status": http_code_value,
811 "detail": error_text,
812 }
sousaedu80135b92021-02-17 15:05:18 +0100813
tierno1d213f42020-04-24 14:02:51 +0000814 return self._format_out(problem_details, token_info)
815 # raise cherrypy.HTTPError(e.http_code.value, str(e))
816 finally:
817 if token_info:
818 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
819 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
820 if outdata.get(logging_id):
sousaedu80135b92021-02-17 15:05:18 +0100821 cherrypy.request.login += ";{}={}".format(
822 logging_id, outdata[logging_id][:36]
823 )
tierno1d213f42020-04-24 14:02:51 +0000824
825
826def _start_service():
827 """
828 Callback function called when cherrypy.engine starts
829 Override configuration with env variables
830 Set database, storage, message configuration
831 Init database with admin/admin user password
832 """
tierno70eeb182020-10-19 16:38:00 +0000833 global ro_server, vim_admin_thread
tierno1d213f42020-04-24 14:02:51 +0000834 # global vim_threads
835 cherrypy.log.error("Starting osm_ng_ro")
836 # update general cherrypy configuration
837 update_dict = {}
sousaedu80135b92021-02-17 15:05:18 +0100838 engine_config = cherrypy.tree.apps["/ro"].config
tierno1d213f42020-04-24 14:02:51 +0000839
tierno1d213f42020-04-24 14:02:51 +0000840 for k, v in environ.items():
841 if not k.startswith("OSMRO_"):
842 continue
sousaedu80135b92021-02-17 15:05:18 +0100843
tierno1d213f42020-04-24 14:02:51 +0000844 k1, _, k2 = k[6:].lower().partition("_")
sousaedu80135b92021-02-17 15:05:18 +0100845
tierno1d213f42020-04-24 14:02:51 +0000846 if not k2:
847 continue
sousaedu80135b92021-02-17 15:05:18 +0100848
tierno1d213f42020-04-24 14:02:51 +0000849 try:
850 if k1 in ("server", "test", "auth", "log"):
851 # update [global] configuration
sousaedu80135b92021-02-17 15:05:18 +0100852 update_dict[k1 + "." + k2] = yaml.safe_load(v)
tierno1d213f42020-04-24 14:02:51 +0000853 elif k1 == "static":
854 # update [/static] configuration
855 engine_config["/static"]["tools.staticdir." + k2] = yaml.safe_load(v)
856 elif k1 == "tools":
857 # update [/] configuration
sousaedu80135b92021-02-17 15:05:18 +0100858 engine_config["/"]["tools." + k2.replace("_", ".")] = yaml.safe_load(v)
aticig1ac189e2022-06-30 19:29:04 +0300859 elif k1 in ("message", "database", "storage", "authentication", "period"):
tierno70eeb182020-10-19 16:38:00 +0000860 engine_config[k1][k2] = yaml.safe_load(v)
tierno1d213f42020-04-24 14:02:51 +0000861
862 except Exception as e:
863 raise RoException("Cannot load env '{}': {}".format(k, e))
864
865 if update_dict:
866 cherrypy.config.update(update_dict)
867 engine_config["global"].update(update_dict)
868
869 # logging cherrypy
sousaedu80135b92021-02-17 15:05:18 +0100870 log_format_simple = (
871 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
872 )
873 log_formatter_simple = logging.Formatter(
874 log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S"
875 )
tierno1d213f42020-04-24 14:02:51 +0000876 logger_server = logging.getLogger("cherrypy.error")
877 logger_access = logging.getLogger("cherrypy.access")
878 logger_cherry = logging.getLogger("cherrypy")
sousaedue493e9b2021-02-09 15:30:01 +0100879 logger = logging.getLogger("ro")
tierno1d213f42020-04-24 14:02:51 +0000880
881 if "log.file" in engine_config["global"]:
sousaedu80135b92021-02-17 15:05:18 +0100882 file_handler = logging.handlers.RotatingFileHandler(
883 engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0
884 )
tierno1d213f42020-04-24 14:02:51 +0000885 file_handler.setFormatter(log_formatter_simple)
886 logger_cherry.addHandler(file_handler)
sousaedue493e9b2021-02-09 15:30:01 +0100887 logger.addHandler(file_handler)
sousaedu80135b92021-02-17 15:05:18 +0100888
tierno1d213f42020-04-24 14:02:51 +0000889 # log always to standard output
sousaedu80135b92021-02-17 15:05:18 +0100890 for format_, logger in {
891 "ro.server %(filename)s:%(lineno)s": logger_server,
892 "ro.access %(filename)s:%(lineno)s": logger_access,
893 "%(name)s %(filename)s:%(lineno)s": logger,
894 }.items():
tierno1d213f42020-04-24 14:02:51 +0000895 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
sousaedu80135b92021-02-17 15:05:18 +0100896 log_formatter_cherry = logging.Formatter(
897 log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S"
898 )
tierno1d213f42020-04-24 14:02:51 +0000899 str_handler = logging.StreamHandler()
900 str_handler.setFormatter(log_formatter_cherry)
901 logger.addHandler(str_handler)
902
903 if engine_config["global"].get("log.level"):
904 logger_cherry.setLevel(engine_config["global"]["log.level"])
sousaedue493e9b2021-02-09 15:30:01 +0100905 logger.setLevel(engine_config["global"]["log.level"])
sousaedu80135b92021-02-17 15:05:18 +0100906
tierno1d213f42020-04-24 14:02:51 +0000907 # logging other modules
sousaedu80135b92021-02-17 15:05:18 +0100908 for k1, logname in {
909 "message": "ro.msg",
910 "database": "ro.db",
911 "storage": "ro.fs",
912 }.items():
tierno1d213f42020-04-24 14:02:51 +0000913 engine_config[k1]["logger_name"] = logname
914 logger_module = logging.getLogger(logname)
sousaedu80135b92021-02-17 15:05:18 +0100915
tierno1d213f42020-04-24 14:02:51 +0000916 if "logfile" in engine_config[k1]:
sousaedu80135b92021-02-17 15:05:18 +0100917 file_handler = logging.handlers.RotatingFileHandler(
918 engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0
919 )
tierno1d213f42020-04-24 14:02:51 +0000920 file_handler.setFormatter(log_formatter_simple)
921 logger_module.addHandler(file_handler)
sousaedu80135b92021-02-17 15:05:18 +0100922
tierno1d213f42020-04-24 14:02:51 +0000923 if "loglevel" in engine_config[k1]:
924 logger_module.setLevel(engine_config[k1]["loglevel"])
925 # TODO add more entries, e.g.: storage
926
927 engine_config["assignment"] = {}
928 # ^ 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 +0100929 cherrypy.tree.apps["/ro"].root.ns.start(engine_config)
930 cherrypy.tree.apps["/ro"].root.authenticator.start(engine_config)
931 cherrypy.tree.apps["/ro"].root.ns.init_db(target_version=database_version)
tierno1d213f42020-04-24 14:02:51 +0000932
933 # # start subscriptions thread:
tierno70eeb182020-10-19 16:38:00 +0000934 vim_admin_thread = VimAdminThread(config=engine_config, engine=ro_server.ns)
935 vim_admin_thread.start()
Gulsum Aticid586d892023-02-13 18:40:03 +0300936 start_monitoring(config=engine_config)
937
tierno1d213f42020-04-24 14:02:51 +0000938 # # Do not capture except SubscriptionException
939
tierno70eeb182020-10-19 16:38:00 +0000940 # backend = engine_config["authentication"]["backend"]
941 # cherrypy.log.error("Starting OSM NBI Version '{} {}' with '{}' authentication backend"
942 # .format(ro_version, ro_version_date, backend))
tierno1d213f42020-04-24 14:02:51 +0000943
944
945def _stop_service():
946 """
947 Callback function called when cherrypy.engine stops
948 TODO: Ending database connections.
949 """
tierno70eeb182020-10-19 16:38:00 +0000950 global vim_admin_thread
sousaedu80135b92021-02-17 15:05:18 +0100951
tierno70eeb182020-10-19 16:38:00 +0000952 # terminate vim_admin_thread
953 if vim_admin_thread:
954 vim_admin_thread.terminate()
Gulsum Aticid586d892023-02-13 18:40:03 +0300955 stop_monitoring()
tierno70eeb182020-10-19 16:38:00 +0000956 vim_admin_thread = None
sousaedu80135b92021-02-17 15:05:18 +0100957 cherrypy.tree.apps["/ro"].root.ns.stop()
tierno1d213f42020-04-24 14:02:51 +0000958 cherrypy.log.error("Stopping osm_ng_ro")
959
960
961def ro_main(config_file):
962 global ro_server
sousaedu80135b92021-02-17 15:05:18 +0100963
tierno1d213f42020-04-24 14:02:51 +0000964 ro_server = Server()
sousaedu80135b92021-02-17 15:05:18 +0100965 cherrypy.engine.subscribe("start", _start_service)
966 cherrypy.engine.subscribe("stop", _stop_service)
967 cherrypy.quickstart(ro_server, "/ro", config_file)
tierno1d213f42020-04-24 14:02:51 +0000968
969
970def usage():
sousaedu80135b92021-02-17 15:05:18 +0100971 print(
972 """Usage: {} [options]
tierno1d213f42020-04-24 14:02:51 +0000973 -c|--config [configuration_file]: loads the configuration file (default: ./ro.cfg)
974 -h|--help: shows this help
sousaedu80135b92021-02-17 15:05:18 +0100975 """.format(
976 sys.argv[0]
977 )
978 )
tierno1d213f42020-04-24 14:02:51 +0000979 # --log-socket-host HOST: send logs to this host")
980 # --log-socket-port PORT: send logs using this port (default: 9022)")
981
982
sousaedu80135b92021-02-17 15:05:18 +0100983if __name__ == "__main__":
tierno1d213f42020-04-24 14:02:51 +0000984 try:
985 # load parameters and configuration
986 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
987 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
988 config_file = None
sousaedu80135b92021-02-17 15:05:18 +0100989
tierno1d213f42020-04-24 14:02:51 +0000990 for o, a in opts:
991 if o in ("-h", "--help"):
992 usage()
993 sys.exit()
994 elif o in ("-c", "--config"):
995 config_file = a
996 else:
aticig7b521f72022-07-15 00:43:09 +0300997 raise ValueError("Unhandled option")
sousaedu80135b92021-02-17 15:05:18 +0100998
tierno1d213f42020-04-24 14:02:51 +0000999 if config_file:
1000 if not path.isfile(config_file):
sousaedu80135b92021-02-17 15:05:18 +01001001 print(
1002 "configuration file '{}' that not exist".format(config_file),
1003 file=sys.stderr,
1004 )
tierno1d213f42020-04-24 14:02:51 +00001005 exit(1)
1006 else:
sousaedu80135b92021-02-17 15:05:18 +01001007 for config_file in (
1008 path.dirname(__file__) + "/ro.cfg",
1009 "./ro.cfg",
1010 "/etc/osm/ro.cfg",
1011 ):
tierno1d213f42020-04-24 14:02:51 +00001012 if path.isfile(config_file):
1013 break
1014 else:
sousaedu80135b92021-02-17 15:05:18 +01001015 print(
1016 "No configuration file 'ro.cfg' found neither at local folder nor at /etc/osm/",
1017 file=sys.stderr,
1018 )
tierno1d213f42020-04-24 14:02:51 +00001019 exit(1)
sousaedu80135b92021-02-17 15:05:18 +01001020
tierno1d213f42020-04-24 14:02:51 +00001021 ro_main(config_file)
tierno70eeb182020-10-19 16:38:00 +00001022 except KeyboardInterrupt:
1023 print("KeyboardInterrupt. Finishing", file=sys.stderr)
tierno1d213f42020-04-24 14:02:51 +00001024 except getopt.GetoptError as e:
1025 print(str(e), file=sys.stderr)
1026 # usage()
1027 exit(1)