Fix bug 771 Do not revoke token when try to do a non allowed operation
[osm/NBI.git] / osm_nbi / nbi.py
1 #!/usr/bin/python3
2 # -*- coding: utf-8 -*-
3
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13 # implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 import cherrypy
18 import time
19 import json
20 import yaml
21 import html_out as html
22 import logging
23 import logging.handlers
24 import getopt
25 import sys
26
27 from authconn import AuthException
28 from auth import Authenticator
29 from engine import Engine, EngineException
30 from subscriptions import SubscriptionThread
31 from validation import ValidationError
32 from osm_common.dbbase import DbException
33 from osm_common.fsbase import FsException
34 from osm_common.msgbase import MsgException
35 from http import HTTPStatus
36 from codecs import getreader
37 from os import environ, path
38
39 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
40
41 __version__ = "0.1.3"
42 version_date = "Jan 2019"
43 database_version = '1.2'
44 auth_database_version = '1.0'
45 nbi_server = None # instance of Server class
46 subscription_thread = None # instance of SubscriptionThread class
47
48
49 """
50 North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
51 URL: /osm GET POST PUT DELETE PATCH
52 /nsd/v1
53 /ns_descriptors_content O O
54 /<nsdInfoId> O O O O
55 /ns_descriptors O5 O5
56 /<nsdInfoId> O5 O5 5
57 /nsd_content O5 O5
58 /nsd O
59 /artifacts[/<artifactPath>] O
60 /pnf_descriptors 5 5
61 /<pnfdInfoId> 5 5 5
62 /pnfd_content 5 5
63 /subscriptions 5 5
64 /<subscriptionId> 5 X
65
66 /vnfpkgm/v1
67 /vnf_packages_content O O
68 /<vnfPkgId> O O
69 /vnf_packages O5 O5
70 /<vnfPkgId> O5 O5 5
71 /package_content O5 O5
72 /upload_from_uri X
73 /vnfd O5
74 /artifacts[/<artifactPath>] O5
75 /subscriptions X X
76 /<subscriptionId> X X
77
78 /nslcm/v1
79 /ns_instances_content O O
80 /<nsInstanceId> O O
81 /ns_instances 5 5
82 /<nsInstanceId> O5 O5
83 instantiate O5
84 terminate O5
85 action O
86 scale O5
87 heal 5
88 /ns_lcm_op_occs 5 5
89 /<nsLcmOpOccId> 5 5 5
90 TO BE COMPLETED 5 5
91 /vnf_instances (also vnfrs for compatibility) O
92 /<vnfInstanceId> O
93 /subscriptions 5 5
94 /<subscriptionId> 5 X
95
96 /pdu/v1
97 /pdu_descriptors O O
98 /<id> O O O O
99
100 /admin/v1
101 /tokens O O
102 /<id> O O
103 /users O O
104 /<id> O O O O
105 /projects O O
106 /<id> O O
107 /vim_accounts (also vims for compatibility) O O
108 /<id> O O O
109 /wim_accounts O O
110 /<id> O O O
111 /sdns O O
112 /<id> O O O
113
114 /nst/v1 O O
115 /netslice_templates_content O O
116 /<nstInfoId> O O O O
117 /netslice_templates O O
118 /<nstInfoId> O O O
119 /nst_content O O
120 /nst O
121 /artifacts[/<artifactPath>] O
122 /subscriptions X X
123 /<subscriptionId> X X
124
125 /nsilcm/v1
126 /netslice_instances_content O O
127 /<SliceInstanceId> O O
128 /netslice_instances O O
129 /<SliceInstanceId> O O
130 instantiate O
131 terminate O
132 action O
133 /nsi_lcm_op_occs O O
134 /<nsiLcmOpOccId> O O O
135 /subscriptions X X
136 /<subscriptionId> X X
137
138 query string:
139 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
140 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
141 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
142 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
143 attrName := string
144 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
145 item of the array, that is, pass if any item of the array pass the filter.
146 It allows both ne and neq for not equal
147 TODO: 4.3.3 Attribute selectors
148 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
149 (none) … same as “exclude_default”
150 all_fields … all attributes.
151 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
152 conditionally mandatory, and that are not provided in <list>.
153 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
154 are not conditionally mandatory, and that are provided in <list>.
155 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
156 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
157 the particular resource
158 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
159 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
160 present specification for the particular resource, but that are not part of <list>
161 Additionally it admits some administrator values:
162 FORCE: To force operations skipping dependency checkings
163 ADMIN: To act as an administrator or a different project
164 PUBLIC: To get public descriptors or set a descriptor as public
165 SET_PROJECT: To make a descriptor available for other project
166
167 Header field name Reference Example Descriptions
168 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
169 This header field shall be present if the response is expected to have a non-empty message body.
170 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
171 This header field shall be present if the request has a non-empty message body.
172 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
173 Details are specified in clause 4.5.3.
174 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
175 Header field name Reference Example Descriptions
176 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
177 This header field shall be present if the response has a non-empty message body.
178 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
179 new resource has been created.
180 This header field shall be present if the response status code is 201 or 3xx.
181 In the present document this header field is also used if the response status code is 202 and a new resource was
182 created.
183 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
184 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
185 token.
186 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
187 certain resources.
188 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
189 response, and the total length of the file.
190 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
191 """
192
193
194 class NbiException(Exception):
195
196 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
197 Exception.__init__(self, message)
198 self.http_code = http_code
199
200
201 class 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.engine = Engine()
209 self.authenticator = Authenticator()
210 self.valid_methods = { # contains allowed URL and methods
211 "admin": {
212 "v1": {
213 "tokens": {"METHODS": ("GET", "POST", "DELETE"),
214 "<ID>": {"METHODS": ("GET", "DELETE")}
215 },
216 "users": {"METHODS": ("GET", "POST"),
217 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT")}
218 },
219 "projects": {"METHODS": ("GET", "POST"),
220 "<ID>": {"METHODS": ("GET", "DELETE", "PUT")}
221 },
222 "roles": {"METHODS": ("GET", "POST"),
223 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PUT")}
224 },
225 "vims": {"METHODS": ("GET", "POST"),
226 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
227 },
228 "vim_accounts": {"METHODS": ("GET", "POST"),
229 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
230 },
231 "wim_accounts": {"METHODS": ("GET", "POST"),
232 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
233 },
234 "sdns": {"METHODS": ("GET", "POST"),
235 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
236 },
237 }
238 },
239 "pdu": {
240 "v1": {
241 "pdu_descriptors": {"METHODS": ("GET", "POST"),
242 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT")}
243 },
244 }
245 },
246 "nsd": {
247 "v1": {
248 "ns_descriptors_content": {"METHODS": ("GET", "POST"),
249 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
250 },
251 "ns_descriptors": {"METHODS": ("GET", "POST"),
252 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
253 "nsd_content": {"METHODS": ("GET", "PUT")},
254 "nsd": {"METHODS": "GET"}, # descriptor inside package
255 "artifacts": {"*": {"METHODS": "GET"}}
256 }
257 },
258 "pnf_descriptors": {"TODO": ("GET", "POST"),
259 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
260 "pnfd_content": {"TODO": ("GET", "PUT")}
261 }
262 },
263 "subscriptions": {"TODO": ("GET", "POST"),
264 "<ID>": {"TODO": ("GET", "DELETE")}
265 },
266 }
267 },
268 "vnfpkgm": {
269 "v1": {
270 "vnf_packages_content": {"METHODS": ("GET", "POST"),
271 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
272 },
273 "vnf_packages": {"METHODS": ("GET", "POST"),
274 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
275 "package_content": {"METHODS": ("GET", "PUT"), # package
276 "upload_from_uri": {"TODO": "POST"}
277 },
278 "vnfd": {"METHODS": "GET"}, # descriptor inside package
279 "artifacts": {"*": {"METHODS": "GET"}}
280 }
281 },
282 "subscriptions": {"TODO": ("GET", "POST"),
283 "<ID>": {"TODO": ("GET", "DELETE")}
284 },
285 }
286 },
287 "nslcm": {
288 "v1": {
289 "ns_instances_content": {"METHODS": ("GET", "POST"),
290 "<ID>": {"METHODS": ("GET", "DELETE")}
291 },
292 "ns_instances": {"METHODS": ("GET", "POST"),
293 "<ID>": {"METHODS": ("GET", "DELETE"),
294 "scale": {"METHODS": "POST"},
295 "terminate": {"METHODS": "POST"},
296 "instantiate": {"METHODS": "POST"},
297 "action": {"METHODS": "POST"},
298 }
299 },
300 "ns_lcm_op_occs": {"METHODS": "GET",
301 "<ID>": {"METHODS": "GET"},
302 },
303 "vnfrs": {"METHODS": ("GET"),
304 "<ID>": {"METHODS": ("GET")}
305 },
306 "vnf_instances": {"METHODS": ("GET"),
307 "<ID>": {"METHODS": ("GET")}
308 },
309 }
310 },
311 "nst": {
312 "v1": {
313 "netslice_templates_content": {"METHODS": ("GET", "POST"),
314 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
315 },
316 "netslice_templates": {"METHODS": ("GET", "POST"),
317 "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH",
318 "nst_content": {"METHODS": ("GET", "PUT")},
319 "nst": {"METHODS": "GET"}, # descriptor inside package
320 "artifacts": {"*": {"METHODS": "GET"}}
321 }
322 },
323 "subscriptions": {"TODO": ("GET", "POST"),
324 "<ID>": {"TODO": ("GET", "DELETE")}
325 },
326 }
327 },
328 "nsilcm": {
329 "v1": {
330 "netslice_instances_content": {"METHODS": ("GET", "POST"),
331 "<ID>": {"METHODS": ("GET", "DELETE")}
332 },
333 "netslice_instances": {"METHODS": ("GET", "POST"),
334 "<ID>": {"METHODS": ("GET", "DELETE"),
335 "terminate": {"METHODS": "POST"},
336 "instantiate": {"METHODS": "POST"},
337 "action": {"METHODS": "POST"},
338 }
339 },
340 "nsi_lcm_op_occs": {"METHODS": "GET",
341 "<ID>": {"METHODS": "GET"},
342 },
343 }
344 },
345 "nspm": {
346 "v1": {
347 "pm_jobs": {
348 "<ID>": {
349 "reports": {
350 "<ID>": {"METHODS": ("GET")}
351 }
352 },
353 },
354 },
355 },
356 }
357
358 def _format_in(self, kwargs):
359 try:
360 indata = None
361 if cherrypy.request.body.length:
362 error_text = "Invalid input format "
363
364 if "Content-Type" in cherrypy.request.headers:
365 if "application/json" in cherrypy.request.headers["Content-Type"]:
366 error_text = "Invalid json format "
367 indata = json.load(self.reader(cherrypy.request.body))
368 cherrypy.request.headers.pop("Content-File-MD5", None)
369 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
370 error_text = "Invalid yaml format "
371 indata = yaml.load(cherrypy.request.body)
372 cherrypy.request.headers.pop("Content-File-MD5", None)
373 elif "application/binary" in cherrypy.request.headers["Content-Type"] or \
374 "application/gzip" in cherrypy.request.headers["Content-Type"] or \
375 "application/zip" in cherrypy.request.headers["Content-Type"] or \
376 "text/plain" in cherrypy.request.headers["Content-Type"]:
377 indata = cherrypy.request.body # .read()
378 elif "multipart/form-data" in cherrypy.request.headers["Content-Type"]:
379 if "descriptor_file" in kwargs:
380 filecontent = kwargs.pop("descriptor_file")
381 if not filecontent.file:
382 raise NbiException("empty file or content", HTTPStatus.BAD_REQUEST)
383 indata = filecontent.file # .read()
384 if filecontent.content_type.value:
385 cherrypy.request.headers["Content-Type"] = filecontent.content_type.value
386 else:
387 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
388 # "Only 'Content-Type' of type 'application/json' or
389 # 'application/yaml' for input format are available")
390 error_text = "Invalid yaml format "
391 indata = yaml.load(cherrypy.request.body)
392 cherrypy.request.headers.pop("Content-File-MD5", None)
393 else:
394 error_text = "Invalid yaml format "
395 indata = yaml.load(cherrypy.request.body)
396 cherrypy.request.headers.pop("Content-File-MD5", None)
397 if not indata:
398 indata = {}
399
400 format_yaml = False
401 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
402 format_yaml = True
403
404 for k, v in kwargs.items():
405 if isinstance(v, str):
406 if v == "":
407 kwargs[k] = None
408 elif format_yaml:
409 try:
410 kwargs[k] = yaml.load(v)
411 except Exception:
412 pass
413 elif k.endswith(".gt") or k.endswith(".lt") or k.endswith(".gte") or k.endswith(".lte"):
414 try:
415 kwargs[k] = int(v)
416 except Exception:
417 try:
418 kwargs[k] = float(v)
419 except Exception:
420 pass
421 elif v.find(",") > 0:
422 kwargs[k] = v.split(",")
423 elif isinstance(v, (list, tuple)):
424 for index in range(0, len(v)):
425 if v[index] == "":
426 v[index] = None
427 elif format_yaml:
428 try:
429 v[index] = yaml.load(v[index])
430 except Exception:
431 pass
432
433 return indata
434 except (ValueError, yaml.YAMLError) as exc:
435 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
436 except KeyError as exc:
437 raise NbiException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
438 except Exception as exc:
439 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
440
441 @staticmethod
442 def _format_out(data, session=None, _format=None):
443 """
444 return string of dictionary data according to requested json, yaml, xml. By default json
445 :param data: response to be sent. Can be a dict, text or file
446 :param session:
447 :param _format: The format to be set as Content-Type ir data is a file
448 :return: None
449 """
450 accept = cherrypy.request.headers.get("Accept")
451 if data is None:
452 if accept and "text/html" in accept:
453 return html.format(data, cherrypy.request, cherrypy.response, session)
454 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
455 return
456 elif hasattr(data, "read"): # file object
457 if _format:
458 cherrypy.response.headers["Content-Type"] = _format
459 elif "b" in data.mode: # binariy asssumig zip
460 cherrypy.response.headers["Content-Type"] = 'application/zip'
461 else:
462 cherrypy.response.headers["Content-Type"] = 'text/plain'
463 # TODO check that cherrypy close file. If not implement pending things to close per thread next
464 return data
465 if accept:
466 if "application/json" in accept:
467 cherrypy.response.headers["Content-Type"] = 'application/json; charset=utf-8'
468 a = json.dumps(data, indent=4) + "\n"
469 return a.encode("utf8")
470 elif "text/html" in accept:
471 return html.format(data, cherrypy.request, cherrypy.response, session)
472
473 elif "application/yaml" in accept or "*/*" in accept or "text/plain" in accept:
474 pass
475 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
476 elif cherrypy.response.status >= 400:
477 raise cherrypy.HTTPError(HTTPStatus.NOT_ACCEPTABLE.value,
478 "Only 'Accept' of type 'application/json' or 'application/yaml' "
479 "for output format are available")
480 cherrypy.response.headers["Content-Type"] = 'application/yaml'
481 return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False,
482 encoding='utf-8', allow_unicode=True) # , canonical=True, default_style='"'
483
484 @cherrypy.expose
485 def index(self, *args, **kwargs):
486 session = None
487 try:
488 if cherrypy.request.method == "GET":
489 session = self.authenticator.authorize()
490 outdata = "Index page"
491 else:
492 raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value,
493 "Method {} not allowed for tokens".format(cherrypy.request.method))
494
495 return self._format_out(outdata, session)
496
497 except (EngineException, AuthException) as e:
498 cherrypy.log("index Exception {}".format(e))
499 cherrypy.response.status = e.http_code.value
500 return self._format_out("Welcome to OSM!", session)
501
502 @cherrypy.expose
503 def version(self, *args, **kwargs):
504 # TODO consider to remove and provide version using the static version file
505 global __version__, version_date
506 try:
507 if cherrypy.request.method != "GET":
508 raise NbiException("Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED)
509 elif args or kwargs:
510 raise NbiException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED)
511 return __version__ + " " + version_date
512 except NbiException as e:
513 cherrypy.response.status = e.http_code.value
514 problem_details = {
515 "code": e.http_code.name,
516 "status": e.http_code.value,
517 "detail": str(e),
518 }
519 return self._format_out(problem_details, None)
520
521 @cherrypy.expose
522 def token(self, method, token_id=None, kwargs=None):
523 session = None
524 # self.engine.load_dbase(cherrypy.request.app.config)
525 indata = self._format_in(kwargs)
526 if not isinstance(indata, dict):
527 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST)
528 try:
529 if method == "GET":
530 session = self.authenticator.authorize()
531 if token_id:
532 outdata = self.authenticator.get_token(session, token_id)
533 else:
534 outdata = self.authenticator.get_token_list(session)
535 elif method == "POST":
536 try:
537 session = self.authenticator.authorize()
538 except Exception:
539 session = None
540 if kwargs:
541 indata.update(kwargs)
542 outdata = self.authenticator.new_token(session, indata, cherrypy.request.remote)
543 session = outdata
544 cherrypy.session['Authorization'] = outdata["_id"]
545 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
546 # cherrypy.response.cookie["Authorization"] = outdata["id"]
547 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
548 elif method == "DELETE":
549 if not token_id and "id" in kwargs:
550 token_id = kwargs["id"]
551 elif not token_id:
552 session = self.authenticator.authorize()
553 token_id = session["_id"]
554 outdata = self.authenticator.del_token(token_id)
555 session = None
556 cherrypy.session['Authorization'] = "logout"
557 # cherrypy.response.cookie["Authorization"] = token_id
558 # cherrypy.response.cookie["Authorization"]['expires'] = 0
559 else:
560 raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
561 return self._format_out(outdata, session)
562 except (NbiException, EngineException, DbException, AuthException) as e:
563 cherrypy.log("tokens Exception {}".format(e))
564 cherrypy.response.status = e.http_code.value
565 problem_details = {
566 "code": e.http_code.name,
567 "status": e.http_code.value,
568 "detail": str(e),
569 }
570 return self._format_out(problem_details, session)
571
572 @cherrypy.expose
573 def test(self, *args, **kwargs):
574 thread_info = None
575 if args and args[0] == "help":
576 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
577 "sleep/<time>\nmessage/topic\n</pre></html>"
578
579 elif args and args[0] == "init":
580 try:
581 # self.engine.load_dbase(cherrypy.request.app.config)
582 self.engine.create_admin()
583 return "Done. User 'admin', password 'admin' created"
584 except Exception:
585 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
586 return self._format_out("Database already initialized")
587 elif args and args[0] == "file":
588 return cherrypy.lib.static.serve_file(cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1],
589 "text/plain", "attachment")
590 elif args and args[0] == "file2":
591 f_path = cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1]
592 f = open(f_path, "r")
593 cherrypy.response.headers["Content-type"] = "text/plain"
594 return f
595
596 elif len(args) == 2 and args[0] == "db-clear":
597 deleted_info = self.engine.db.del_list(args[1], kwargs)
598 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
599 elif len(args) and args[0] == "fs-clear":
600 if len(args) >= 2:
601 folders = (args[1],)
602 else:
603 folders = self.engine.fs.dir_ls(".")
604 for folder in folders:
605 self.engine.fs.file_delete(folder)
606 return ",".join(folders) + " folders deleted\n"
607 elif args and args[0] == "login":
608 if not cherrypy.request.headers.get("Authorization"):
609 cherrypy.response.headers["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
610 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
611 elif args and args[0] == "login2":
612 if not cherrypy.request.headers.get("Authorization"):
613 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
614 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
615 elif args and args[0] == "sleep":
616 sleep_time = 5
617 try:
618 sleep_time = int(args[1])
619 except Exception:
620 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
621 return self._format_out("Database already initialized")
622 thread_info = cherrypy.thread_data
623 print(thread_info)
624 time.sleep(sleep_time)
625 # thread_info
626 elif len(args) >= 2 and args[0] == "message":
627 main_topic = args[1]
628 return_text = "<html><pre>{} ->\n".format(main_topic)
629 try:
630 if cherrypy.request.method == 'POST':
631 to_send = yaml.load(cherrypy.request.body)
632 for k, v in to_send.items():
633 self.engine.msg.write(main_topic, k, v)
634 return_text += " {}: {}\n".format(k, v)
635 elif cherrypy.request.method == 'GET':
636 for k, v in kwargs.items():
637 self.engine.msg.write(main_topic, k, yaml.load(v))
638 return_text += " {}: {}\n".format(k, yaml.load(v))
639 except Exception as e:
640 return_text += "Error: " + str(e)
641 return_text += "</pre></html>\n"
642 return return_text
643
644 return_text = (
645 "<html><pre>\nheaders:\n args: {}\n".format(args) +
646 " kwargs: {}\n".format(kwargs) +
647 " headers: {}\n".format(cherrypy.request.headers) +
648 " path_info: {}\n".format(cherrypy.request.path_info) +
649 " query_string: {}\n".format(cherrypy.request.query_string) +
650 " session: {}\n".format(cherrypy.session) +
651 " cookie: {}\n".format(cherrypy.request.cookie) +
652 " method: {}\n".format(cherrypy.request.method) +
653 " session: {}\n".format(cherrypy.session.get('fieldname')) +
654 " body:\n")
655 return_text += " length: {}\n".format(cherrypy.request.body.length)
656 if cherrypy.request.body.length:
657 return_text += " content: {}\n".format(
658 str(cherrypy.request.body.read(int(cherrypy.request.headers.get('Content-Length', 0)))))
659 if thread_info:
660 return_text += "thread: {}\n".format(thread_info)
661 return_text += "</pre></html>"
662 return return_text
663
664 def _check_valid_url_method(self, method, *args):
665 if len(args) < 3:
666 raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus.METHOD_NOT_ALLOWED)
667
668 reference = self.valid_methods
669 for arg in args:
670 if arg is None:
671 break
672 if not isinstance(reference, dict):
673 raise NbiException("URL contains unexpected extra items '{}'".format(arg),
674 HTTPStatus.METHOD_NOT_ALLOWED)
675
676 if arg in reference:
677 reference = reference[arg]
678 elif "<ID>" in reference:
679 reference = reference["<ID>"]
680 elif "*" in reference:
681 reference = reference["*"]
682 break
683 else:
684 raise NbiException("Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED)
685 if "TODO" in reference and method in reference["TODO"]:
686 raise NbiException("Method {} not supported yet for this URL".format(method), HTTPStatus.NOT_IMPLEMENTED)
687 elif "METHODS" in reference and method not in reference["METHODS"]:
688 raise NbiException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
689 return
690
691 @staticmethod
692 def _set_location_header(main_topic, version, topic, id):
693 """
694 Insert response header Location with the URL of created item base on URL params
695 :param main_topic:
696 :param version:
697 :param topic:
698 :param id:
699 :return: None
700 """
701 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
702 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(main_topic, version, topic, id)
703 return
704
705 @staticmethod
706 def _manage_admin_query(session, kwargs, method, _id):
707 """
708 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
709 Check that users has rights to use them and returs the admin_query
710 :param session: session rights obtained by token
711 :param kwargs: query string input.
712 :param method: http method: GET, POSST, PUT, ...
713 :param _id:
714 :return: admin_query dictionary with keys:
715 public: True, False or None
716 force: True or False
717 project_id: tuple with projects used for accessing an element
718 set_project: tuple with projects that a created element will belong to
719 method: show, list, delete, write
720 """
721 admin_query = {"force": False, "project_id": (session["project_id"], ), "username": session["username"],
722 "admin": session["admin"], "public": None}
723 if kwargs:
724 # FORCE
725 if "FORCE" in kwargs:
726 if kwargs["FORCE"].lower() != "false": # if None or True set force to True
727 admin_query["force"] = True
728 del kwargs["FORCE"]
729 # PUBLIC
730 if "PUBLIC" in kwargs:
731 if kwargs["PUBLIC"].lower() != "false": # if None or True set public to True
732 admin_query["public"] = True
733 else:
734 admin_query["public"] = False
735 del kwargs["PUBLIC"]
736 # ADMIN
737 if "ADMIN" in kwargs:
738 behave_as = kwargs.pop("ADMIN")
739 if behave_as.lower() != "false":
740 if not session["admin"]:
741 raise NbiException("Only admin projects can use 'ADMIN' query string", HTTPStatus.UNAUTHORIZED)
742 if not behave_as or behave_as.lower() == "true": # convert True, None to empty list
743 admin_query["project_id"] = ()
744 elif isinstance(behave_as, (list, tuple)):
745 admin_query["project_id"] = behave_as
746 else: # isinstance(behave_as, str)
747 admin_query["project_id"] = (behave_as, )
748 if "SET_PROJECT" in kwargs:
749 set_project = kwargs.pop("SET_PROJECT")
750 if not set_project:
751 admin_query["set_project"] = list(admin_query["project_id"])
752 else:
753 if isinstance(set_project, str):
754 set_project = (set_project, )
755 if admin_query["project_id"]:
756 for p in set_project:
757 if p not in admin_query["project_id"]:
758 raise NbiException("Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
759 "'ADMIN='{p}'".format(p=p), HTTPStatus.UNAUTHORIZED)
760 admin_query["set_project"] = set_project
761
762 # PROJECT_READ
763 # if "PROJECT_READ" in kwargs:
764 # admin_query["project"] = kwargs.pop("project")
765 # if admin_query["project"] == session["project_id"]:
766 if method == "GET":
767 if _id:
768 admin_query["method"] = "show"
769 else:
770 admin_query["method"] = "list"
771 elif method == "DELETE":
772 admin_query["method"] = "delete"
773 else:
774 admin_query["method"] = "write"
775 return admin_query
776
777 @cherrypy.expose
778 def default(self, main_topic=None, version=None, topic=None, _id=None, item=None, *args, **kwargs):
779 session = None
780 outdata = None
781 _format = None
782 method = "DONE"
783 engine_topic = None
784 rollback = []
785 session = None
786 try:
787 if not main_topic or not version or not topic:
788 raise NbiException("URL must contain at least 'main_topic/version/topic'",
789 HTTPStatus.METHOD_NOT_ALLOWED)
790 if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm", "nspm"):
791 raise NbiException("URL main_topic '{}' not supported".format(main_topic),
792 HTTPStatus.METHOD_NOT_ALLOWED)
793 if version != 'v1':
794 raise NbiException("URL version '{}' not supported".format(version), HTTPStatus.METHOD_NOT_ALLOWED)
795
796 if kwargs and "METHOD" in kwargs and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
797 method = kwargs.pop("METHOD")
798 else:
799 method = cherrypy.request.method
800
801 self._check_valid_url_method(method, main_topic, version, topic, _id, item, *args)
802
803 if main_topic == "admin" and topic == "tokens":
804 return self.token(method, _id, kwargs)
805
806 # self.engine.load_dbase(cherrypy.request.app.config)
807 session = self.authenticator.authorize()
808 session = self._manage_admin_query(session, kwargs, method, _id)
809 indata = self._format_in(kwargs)
810 engine_topic = topic
811 if topic == "subscriptions":
812 engine_topic = main_topic + "_" + topic
813 if item and topic != "pm_jobs":
814 engine_topic = item
815
816 if main_topic == "nsd":
817 engine_topic = "nsds"
818 elif main_topic == "vnfpkgm":
819 engine_topic = "vnfds"
820 elif main_topic == "nslcm":
821 engine_topic = "nsrs"
822 if topic == "ns_lcm_op_occs":
823 engine_topic = "nslcmops"
824 if topic == "vnfrs" or topic == "vnf_instances":
825 engine_topic = "vnfrs"
826 elif main_topic == "nst":
827 engine_topic = "nsts"
828 elif main_topic == "nsilcm":
829 engine_topic = "nsis"
830 if topic == "nsi_lcm_op_occs":
831 engine_topic = "nsilcmops"
832 elif main_topic == "pdu":
833 engine_topic = "pdus"
834 if engine_topic == "vims": # TODO this is for backward compatibility, it will be removed in the future
835 engine_topic = "vim_accounts"
836
837 if method == "GET":
838 if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
839 if item in ("vnfd", "nsd", "nst"):
840 path = "$DESCRIPTOR"
841 elif args:
842 path = args
843 elif item == "artifacts":
844 path = ()
845 else:
846 path = None
847 file, _format = self.engine.get_file(session, engine_topic, _id, path,
848 cherrypy.request.headers.get("Accept"))
849 outdata = file
850 elif not _id:
851 outdata = self.engine.get_item_list(session, engine_topic, kwargs)
852 else:
853 if item == "reports":
854 # TODO check that project_id (_id in this context) has permissions
855 _id = args[0]
856 outdata = self.engine.get_item(session, engine_topic, _id)
857 elif method == "POST":
858 if topic in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"):
859 _id = cherrypy.request.headers.get("Transaction-Id")
860 if not _id:
861 _id = self.engine.new_item(rollback, session, engine_topic, {}, None, cherrypy.request.headers)
862 completed = self.engine.upload_content(session, engine_topic, _id, indata, kwargs,
863 cherrypy.request.headers)
864 if completed:
865 self._set_location_header(main_topic, version, topic, _id)
866 else:
867 cherrypy.response.headers["Transaction-Id"] = _id
868 outdata = {"id": _id}
869 elif topic == "ns_instances_content":
870 # creates NSR
871 _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs)
872 # creates nslcmop
873 indata["lcmOperationType"] = "instantiate"
874 indata["nsInstanceId"] = _id
875 nslcmop_id = self.engine.new_item(rollback, session, "nslcmops", indata, None)
876 self._set_location_header(main_topic, version, topic, _id)
877 outdata = {"id": _id, "nslcmop_id": nslcmop_id}
878 elif topic == "ns_instances" and item:
879 indata["lcmOperationType"] = item
880 indata["nsInstanceId"] = _id
881 _id = self.engine.new_item(rollback, session, "nslcmops", indata, kwargs)
882 self._set_location_header(main_topic, version, "ns_lcm_op_occs", _id)
883 outdata = {"id": _id}
884 cherrypy.response.status = HTTPStatus.ACCEPTED.value
885 elif topic == "netslice_instances_content":
886 # creates NetSlice_Instance_record (NSIR)
887 _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs)
888 self._set_location_header(main_topic, version, topic, _id)
889 indata["lcmOperationType"] = "instantiate"
890 indata["netsliceInstanceId"] = _id
891 nsilcmop_id = self.engine.new_item(rollback, session, "nsilcmops", indata, kwargs)
892 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
893
894 elif topic == "netslice_instances" and item:
895 indata["lcmOperationType"] = item
896 indata["netsliceInstanceId"] = _id
897 _id = self.engine.new_item(rollback, session, "nsilcmops", indata, kwargs)
898 self._set_location_header(main_topic, version, "nsi_lcm_op_occs", _id)
899 outdata = {"id": _id}
900 cherrypy.response.status = HTTPStatus.ACCEPTED.value
901 else:
902 _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs,
903 cherrypy.request.headers)
904 self._set_location_header(main_topic, version, topic, _id)
905 outdata = {"id": _id}
906 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
907 cherrypy.response.status = HTTPStatus.CREATED.value
908
909 elif method == "DELETE":
910 if not _id:
911 outdata = self.engine.del_item_list(session, engine_topic, kwargs)
912 cherrypy.response.status = HTTPStatus.OK.value
913 else: # len(args) > 1
914 delete_in_process = False
915 if topic == "ns_instances_content" and not session["force"]:
916 nslcmop_desc = {
917 "lcmOperationType": "terminate",
918 "nsInstanceId": _id,
919 "autoremove": True
920 }
921 opp_id = self.engine.new_item(rollback, session, "nslcmops", nslcmop_desc, None)
922 if opp_id:
923 delete_in_process = True
924 outdata = {"_id": opp_id}
925 cherrypy.response.status = HTTPStatus.ACCEPTED.value
926 elif topic == "netslice_instances_content" and not session["force"]:
927 nsilcmop_desc = {
928 "lcmOperationType": "terminate",
929 "netsliceInstanceId": _id,
930 "autoremove": True
931 }
932 opp_id = self.engine.new_item(rollback, session, "nsilcmops", nsilcmop_desc, None)
933 if opp_id:
934 delete_in_process = True
935 outdata = {"_id": opp_id}
936 cherrypy.response.status = HTTPStatus.ACCEPTED.value
937 if not delete_in_process:
938 self.engine.del_item(session, engine_topic, _id)
939 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
940 if engine_topic in ("vim_accounts", "wim_accounts", "sdns"):
941 cherrypy.response.status = HTTPStatus.ACCEPTED.value
942
943 elif method in ("PUT", "PATCH"):
944 outdata = None
945 if not indata and not kwargs and not session.get("set_project"):
946 raise NbiException("Nothing to update. Provide payload and/or query string",
947 HTTPStatus.BAD_REQUEST)
948 if item in ("nsd_content", "package_content", "nst_content") and method == "PUT":
949 completed = self.engine.upload_content(session, engine_topic, _id, indata, kwargs,
950 cherrypy.request.headers)
951 if not completed:
952 cherrypy.response.headers["Transaction-Id"] = id
953 else:
954 self.engine.edit_item(session, engine_topic, _id, indata, kwargs)
955 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
956 else:
957 raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
958
959 # if Role information changes, it is needed to reload the information of roles
960 if topic == "roles" and method != "GET":
961 self.authenticator.load_operation_to_allowed_roles()
962 return self._format_out(outdata, session, _format)
963 except Exception as e:
964 if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException,
965 ValidationError)):
966 http_code_value = cherrypy.response.status = e.http_code.value
967 http_code_name = e.http_code.name
968 cherrypy.log("Exception {}".format(e))
969 else:
970 http_code_value = cherrypy.response.status = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
971 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
972 http_code_name = HTTPStatus.BAD_REQUEST.name
973 if hasattr(outdata, "close"): # is an open file
974 outdata.close()
975 error_text = str(e)
976 rollback.reverse()
977 for rollback_item in rollback:
978 try:
979 if rollback_item.get("operation") == "set":
980 self.engine.db.set_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
981 rollback_item["content"], fail_on_empty=False)
982 else:
983 self.engine.db.del_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
984 fail_on_empty=False)
985 except Exception as e2:
986 rollback_error_text = "Rollback Exception {}: {}".format(rollback_item, e2)
987 cherrypy.log(rollback_error_text)
988 error_text += ". " + rollback_error_text
989 # if isinstance(e, MsgException):
990 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
991 # engine_topic[:-1], method, error_text)
992 problem_details = {
993 "code": http_code_name,
994 "status": http_code_value,
995 "detail": error_text,
996 }
997 return self._format_out(problem_details, session)
998 # raise cherrypy.HTTPError(e.http_code.value, str(e))
999
1000
1001 def _start_service():
1002 """
1003 Callback function called when cherrypy.engine starts
1004 Override configuration with env variables
1005 Set database, storage, message configuration
1006 Init database with admin/admin user password
1007 """
1008 global nbi_server
1009 global subscription_thread
1010 cherrypy.log.error("Starting osm_nbi")
1011 # update general cherrypy configuration
1012 update_dict = {}
1013
1014 engine_config = cherrypy.tree.apps['/osm'].config
1015 for k, v in environ.items():
1016 if not k.startswith("OSMNBI_"):
1017 continue
1018 k1, _, k2 = k[7:].lower().partition("_")
1019 if not k2:
1020 continue
1021 try:
1022 # update static configuration
1023 if k == 'OSMNBI_STATIC_DIR':
1024 engine_config["/static"]['tools.staticdir.dir'] = v
1025 engine_config["/static"]['tools.staticdir.on'] = True
1026 elif k == 'OSMNBI_SOCKET_PORT' or k == 'OSMNBI_SERVER_PORT':
1027 update_dict['server.socket_port'] = int(v)
1028 elif k == 'OSMNBI_SOCKET_HOST' or k == 'OSMNBI_SERVER_HOST':
1029 update_dict['server.socket_host'] = v
1030 elif k1 in ("server", "test", "auth", "log"):
1031 update_dict[k1 + '.' + k2] = v
1032 elif k1 in ("message", "database", "storage", "authentication"):
1033 # k2 = k2.replace('_', '.')
1034 if k2 in ("port", "db_port"):
1035 engine_config[k1][k2] = int(v)
1036 else:
1037 engine_config[k1][k2] = v
1038
1039 except ValueError as e:
1040 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1041 except Exception as e:
1042 cherrypy.log.warn("skipping environ '{}' on exception '{}'".format(k, e))
1043
1044 if update_dict:
1045 cherrypy.config.update(update_dict)
1046 engine_config["global"].update(update_dict)
1047
1048 # logging cherrypy
1049 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1050 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
1051 logger_server = logging.getLogger("cherrypy.error")
1052 logger_access = logging.getLogger("cherrypy.access")
1053 logger_cherry = logging.getLogger("cherrypy")
1054 logger_nbi = logging.getLogger("nbi")
1055
1056 if "log.file" in engine_config["global"]:
1057 file_handler = logging.handlers.RotatingFileHandler(engine_config["global"]["log.file"],
1058 maxBytes=100e6, backupCount=9, delay=0)
1059 file_handler.setFormatter(log_formatter_simple)
1060 logger_cherry.addHandler(file_handler)
1061 logger_nbi.addHandler(file_handler)
1062 # log always to standard output
1063 for format_, logger in {"nbi.server %(filename)s:%(lineno)s": logger_server,
1064 "nbi.access %(filename)s:%(lineno)s": logger_access,
1065 "%(name)s %(filename)s:%(lineno)s": logger_nbi
1066 }.items():
1067 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
1068 log_formatter_cherry = logging.Formatter(log_format_cherry, datefmt='%Y-%m-%dT%H:%M:%S')
1069 str_handler = logging.StreamHandler()
1070 str_handler.setFormatter(log_formatter_cherry)
1071 logger.addHandler(str_handler)
1072
1073 if engine_config["global"].get("log.level"):
1074 logger_cherry.setLevel(engine_config["global"]["log.level"])
1075 logger_nbi.setLevel(engine_config["global"]["log.level"])
1076
1077 # logging other modules
1078 for k1, logname in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
1079 engine_config[k1]["logger_name"] = logname
1080 logger_module = logging.getLogger(logname)
1081 if "logfile" in engine_config[k1]:
1082 file_handler = logging.handlers.RotatingFileHandler(engine_config[k1]["logfile"],
1083 maxBytes=100e6, backupCount=9, delay=0)
1084 file_handler.setFormatter(log_formatter_simple)
1085 logger_module.addHandler(file_handler)
1086 if "loglevel" in engine_config[k1]:
1087 logger_module.setLevel(engine_config[k1]["loglevel"])
1088 # TODO add more entries, e.g.: storage
1089 cherrypy.tree.apps['/osm'].root.engine.start(engine_config)
1090 cherrypy.tree.apps['/osm'].root.authenticator.start(engine_config)
1091 cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version)
1092 cherrypy.tree.apps['/osm'].root.authenticator.init_db(target_version=auth_database_version)
1093
1094 # start subscriptions thread:
1095 subscription_thread = SubscriptionThread(config=engine_config, engine=nbi_server.engine)
1096 subscription_thread.start()
1097 # Do not capture except SubscriptionException
1098
1099 # load and print version. Ignore possible errors, e.g. file not found
1100 try:
1101 with open("{}/version".format(engine_config["/static"]['tools.staticdir.dir'])) as version_file:
1102 version_data = version_file.read()
1103 cherrypy.log.error("Starting OSM NBI Version: {}".format(version_data.replace("\n", " ")))
1104 except Exception:
1105 pass
1106
1107
1108 def _stop_service():
1109 """
1110 Callback function called when cherrypy.engine stops
1111 TODO: Ending database connections.
1112 """
1113 global subscription_thread
1114 if subscription_thread:
1115 subscription_thread.terminate()
1116 subscription_thread = None
1117 cherrypy.tree.apps['/osm'].root.engine.stop()
1118 cherrypy.log.error("Stopping osm_nbi")
1119
1120
1121 def nbi(config_file):
1122 global nbi_server
1123 # conf = {
1124 # '/': {
1125 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1126 # 'tools.sessions.on': True,
1127 # 'tools.response_headers.on': True,
1128 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1129 # }
1130 # }
1131 # cherrypy.Server.ssl_module = 'builtin'
1132 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1133 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1134 # cherrypy.Server.thread_pool = 10
1135 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1136
1137 # cherrypy.config.update({'tools.auth_basic.on': True,
1138 # 'tools.auth_basic.realm': 'localhost',
1139 # 'tools.auth_basic.checkpassword': validate_password})
1140 nbi_server = Server()
1141 cherrypy.engine.subscribe('start', _start_service)
1142 cherrypy.engine.subscribe('stop', _stop_service)
1143 cherrypy.quickstart(nbi_server, '/osm', config_file)
1144
1145
1146 def usage():
1147 print("""Usage: {} [options]
1148 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1149 -h|--help: shows this help
1150 """.format(sys.argv[0]))
1151 # --log-socket-host HOST: send logs to this host")
1152 # --log-socket-port PORT: send logs using this port (default: 9022)")
1153
1154
1155 if __name__ == '__main__':
1156 try:
1157 # load parameters and configuration
1158 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
1159 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1160 config_file = None
1161 for o, a in opts:
1162 if o in ("-h", "--help"):
1163 usage()
1164 sys.exit()
1165 elif o in ("-c", "--config"):
1166 config_file = a
1167 # elif o == "--log-socket-port":
1168 # log_socket_port = a
1169 # elif o == "--log-socket-host":
1170 # log_socket_host = a
1171 # elif o == "--log-file":
1172 # log_file = a
1173 else:
1174 assert False, "Unhandled option"
1175 if config_file:
1176 if not path.isfile(config_file):
1177 print("configuration file '{}' that not exist".format(config_file), file=sys.stderr)
1178 exit(1)
1179 else:
1180 for config_file in (__file__[:__file__.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
1181 if path.isfile(config_file):
1182 break
1183 else:
1184 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys.stderr)
1185 exit(1)
1186 nbi(config_file)
1187 except getopt.GetoptError as e:
1188 print(str(e), file=sys.stderr)
1189 # usage()
1190 exit(1)