Feature 10909: Heal operation for VDU
[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 osm_nbi.html_out as html
22 import logging
23 import logging.handlers
24 import getopt
25 import sys
26
27 from osm_nbi.authconn import AuthException, AuthconnException
28 from osm_nbi.auth import Authenticator
29 from osm_nbi.engine import Engine, EngineException
30 from osm_nbi.subscriptions import SubscriptionThread
31 from osm_nbi.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 from osm_nbi import version as nbi_version, version_date as nbi_version_date
39
40 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
41
42 __version__ = "0.1.3" # file version, not NBI version
43 version_date = "Aug 2019"
44
45 database_version = "1.2"
46 auth_database_version = "1.0"
47 nbi_server = None # instance of Server class
48 subscription_thread = None # instance of SubscriptionThread class
49
50 """
51 North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
52 URL: /osm GET POST PUT DELETE PATCH
53 /nsd/v1
54 /ns_descriptors_content O O
55 /<nsdInfoId> O O O O
56 /ns_descriptors O5 O5
57 /<nsdInfoId> O5 O5 5
58 /nsd_content O5 O5
59 /nsd O
60 /artifacts[/<artifactPath>] O
61 /pnf_descriptors 5 5
62 /<pnfdInfoId> 5 5 5
63 /pnfd_content 5 5
64 /subscriptions 5 5
65 /<subscriptionId> 5 X
66
67 /vnfpkgm/v1
68 /vnf_packages_content O O
69 /<vnfPkgId> O O
70 /vnf_packages O5 O5
71 /<vnfPkgId> O5 O5 5
72 /package_content O5 O5
73 /upload_from_uri X
74 /vnfd O5
75 /artifacts[/<artifactPath>] O5
76 /subscriptions X X
77 /<subscriptionId> X X
78
79 /nslcm/v1
80 /ns_instances_content O O
81 /<nsInstanceId> O O
82 /ns_instances 5 5
83 /<nsInstanceId> O5 O5
84 instantiate O5
85 terminate O5
86 action O
87 scale O5
88 migrate O
89 update 05
90 heal O5
91 /ns_lcm_op_occs 5 5
92 /<nsLcmOpOccId> 5 5 5
93 TO BE COMPLETED 5 5
94 /vnf_instances (also vnfrs for compatibility) O
95 /<vnfInstanceId> O
96 /subscriptions 5 5
97 /<subscriptionId> 5 X
98
99 /pdu/v1
100 /pdu_descriptors O O
101 /<id> O O O O
102
103 /admin/v1
104 /tokens O O
105 /<id> O O
106 /users O O
107 /<id> O O O O
108 /projects O O
109 /<id> O O
110 /vim_accounts (also vims for compatibility) O O
111 /<id> O O O
112 /wim_accounts O O
113 /<id> O O O
114 /sdns O O
115 /<id> O O O
116 /k8sclusters O O
117 /<id> O O O
118 /k8srepos O O
119 /<id> O O
120 /osmrepos O O
121 /<id> O O
122
123 /nst/v1 O O
124 /netslice_templates_content O O
125 /<nstInfoId> O O O O
126 /netslice_templates O O
127 /<nstInfoId> O O O
128 /nst_content O O
129 /nst O
130 /artifacts[/<artifactPath>] O
131 /subscriptions X X
132 /<subscriptionId> X X
133
134 /nsilcm/v1
135 /netslice_instances_content O O
136 /<SliceInstanceId> O O
137 /netslice_instances O O
138 /<SliceInstanceId> O O
139 instantiate O
140 terminate O
141 action O
142 /nsi_lcm_op_occs O O
143 /<nsiLcmOpOccId> O O O
144 /subscriptions X X
145 /<subscriptionId> X X
146
147 query string:
148 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
149 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
150 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
151 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
152 attrName := string
153 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
154 item of the array, that is, pass if any item of the array pass the filter.
155 It allows both ne and neq for not equal
156 TODO: 4.3.3 Attribute selectors
157 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
158 (none) … same as “exclude_default”
159 all_fields … all attributes.
160 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
161 conditionally mandatory, and that are not provided in <list>.
162 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
163 are not conditionally mandatory, and that are provided in <list>.
164 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
165 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
166 the particular resource
167 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
168 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
169 present specification for the particular resource, but that are not part of <list>
170 Additionally it admits some administrator values:
171 FORCE: To force operations skipping dependency checkings
172 ADMIN: To act as an administrator or a different project
173 PUBLIC: To get public descriptors or set a descriptor as public
174 SET_PROJECT: To make a descriptor available for other project
175
176 Header field name Reference Example Descriptions
177 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
178 This header field shall be present if the response is expected to have a non-empty message body.
179 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
180 This header field shall be present if the request has a non-empty message body.
181 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
182 Details are specified in clause 4.5.3.
183 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
184 Header field name Reference Example Descriptions
185 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
186 This header field shall be present if the response has a non-empty message body.
187 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
188 new resource has been created.
189 This header field shall be present if the response status code is 201 or 3xx.
190 In the present document this header field is also used if the response status code is 202 and a new resource was
191 created.
192 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
193 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
194 token.
195 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
196 certain resources.
197 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
198 response, and the total length of the file.
199 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
200 """
201
202 valid_query_string = ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
203 # ^ Contains possible administrative query string words:
204 # ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
205 # (not owned by my session project).
206 # PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
207 # FORCE=True(by default)|False: Force edition/deletion operations
208 # SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
209
210 valid_url_methods = {
211 # contains allowed URL and methods, and the role_permission name
212 "admin": {
213 "v1": {
214 "tokens": {
215 "METHODS": ("GET", "POST", "DELETE"),
216 "ROLE_PERMISSION": "tokens:",
217 "<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"},
218 },
219 "users": {
220 "METHODS": ("GET", "POST"),
221 "ROLE_PERMISSION": "users:",
222 "<ID>": {
223 "METHODS": ("GET", "DELETE", "PATCH"),
224 "ROLE_PERMISSION": "users:id:",
225 },
226 },
227 "projects": {
228 "METHODS": ("GET", "POST"),
229 "ROLE_PERMISSION": "projects:",
230 "<ID>": {
231 "METHODS": ("GET", "DELETE", "PATCH"),
232 "ROLE_PERMISSION": "projects:id:",
233 },
234 },
235 "roles": {
236 "METHODS": ("GET", "POST"),
237 "ROLE_PERMISSION": "roles:",
238 "<ID>": {
239 "METHODS": ("GET", "DELETE", "PATCH"),
240 "ROLE_PERMISSION": "roles:id:",
241 },
242 },
243 "vims": {
244 "METHODS": ("GET", "POST"),
245 "ROLE_PERMISSION": "vims:",
246 "<ID>": {
247 "METHODS": ("GET", "DELETE", "PATCH"),
248 "ROLE_PERMISSION": "vims:id:",
249 },
250 },
251 "vim_accounts": {
252 "METHODS": ("GET", "POST"),
253 "ROLE_PERMISSION": "vim_accounts:",
254 "<ID>": {
255 "METHODS": ("GET", "DELETE", "PATCH"),
256 "ROLE_PERMISSION": "vim_accounts:id:",
257 },
258 },
259 "wim_accounts": {
260 "METHODS": ("GET", "POST"),
261 "ROLE_PERMISSION": "wim_accounts:",
262 "<ID>": {
263 "METHODS": ("GET", "DELETE", "PATCH"),
264 "ROLE_PERMISSION": "wim_accounts:id:",
265 },
266 },
267 "sdns": {
268 "METHODS": ("GET", "POST"),
269 "ROLE_PERMISSION": "sdn_controllers:",
270 "<ID>": {
271 "METHODS": ("GET", "DELETE", "PATCH"),
272 "ROLE_PERMISSION": "sdn_controllers:id:",
273 },
274 },
275 "k8sclusters": {
276 "METHODS": ("GET", "POST"),
277 "ROLE_PERMISSION": "k8sclusters:",
278 "<ID>": {
279 "METHODS": ("GET", "DELETE", "PATCH"),
280 "ROLE_PERMISSION": "k8sclusters:id:",
281 },
282 },
283 "vca": {
284 "METHODS": ("GET", "POST"),
285 "ROLE_PERMISSION": "vca:",
286 "<ID>": {
287 "METHODS": ("GET", "DELETE", "PATCH"),
288 "ROLE_PERMISSION": "vca:id:",
289 },
290 },
291 "k8srepos": {
292 "METHODS": ("GET", "POST"),
293 "ROLE_PERMISSION": "k8srepos:",
294 "<ID>": {
295 "METHODS": ("GET", "DELETE"),
296 "ROLE_PERMISSION": "k8srepos:id:",
297 },
298 },
299 "osmrepos": {
300 "METHODS": ("GET", "POST"),
301 "ROLE_PERMISSION": "osmrepos:",
302 "<ID>": {
303 "METHODS": ("GET", "DELETE", "PATCH"),
304 "ROLE_PERMISSION": "osmrepos:id:",
305 },
306 },
307 "domains": {
308 "METHODS": ("GET",),
309 "ROLE_PERMISSION": "domains:",
310 },
311 }
312 },
313 "pdu": {
314 "v1": {
315 "pdu_descriptors": {
316 "METHODS": ("GET", "POST"),
317 "ROLE_PERMISSION": "pduds:",
318 "<ID>": {
319 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
320 "ROLE_PERMISSION": "pduds:id:",
321 },
322 },
323 }
324 },
325 "nsd": {
326 "v1": {
327 "ns_descriptors_content": {
328 "METHODS": ("GET", "POST"),
329 "ROLE_PERMISSION": "nsds:",
330 "<ID>": {
331 "METHODS": ("GET", "PUT", "DELETE"),
332 "ROLE_PERMISSION": "nsds:id:",
333 },
334 },
335 "ns_descriptors": {
336 "METHODS": ("GET", "POST"),
337 "ROLE_PERMISSION": "nsds:",
338 "<ID>": {
339 "METHODS": ("GET", "DELETE", "PATCH"),
340 "ROLE_PERMISSION": "nsds:id:",
341 "nsd_content": {
342 "METHODS": ("GET", "PUT"),
343 "ROLE_PERMISSION": "nsds:id:content:",
344 },
345 "nsd": {
346 "METHODS": ("GET",), # descriptor inside package
347 "ROLE_PERMISSION": "nsds:id:content:",
348 },
349 "artifacts": {
350 "METHODS": ("GET",),
351 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
352 "*": None,
353 },
354 },
355 },
356 "pnf_descriptors": {
357 "TODO": ("GET", "POST"),
358 "<ID>": {
359 "TODO": ("GET", "DELETE", "PATCH"),
360 "pnfd_content": {"TODO": ("GET", "PUT")},
361 },
362 },
363 "subscriptions": {
364 "TODO": ("GET", "POST"),
365 "<ID>": {"TODO": ("GET", "DELETE")},
366 },
367 }
368 },
369 "vnfpkgm": {
370 "v1": {
371 "vnf_packages_content": {
372 "METHODS": ("GET", "POST"),
373 "ROLE_PERMISSION": "vnfds:",
374 "<ID>": {
375 "METHODS": ("GET", "PUT", "DELETE"),
376 "ROLE_PERMISSION": "vnfds:id:",
377 },
378 },
379 "vnf_packages": {
380 "METHODS": ("GET", "POST"),
381 "ROLE_PERMISSION": "vnfds:",
382 "<ID>": {
383 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
384 "ROLE_PERMISSION": "vnfds:id:",
385 "package_content": {
386 "METHODS": ("GET", "PUT"), # package
387 "ROLE_PERMISSION": "vnfds:id:",
388 "upload_from_uri": {
389 "METHODS": (),
390 "TODO": ("POST",),
391 "ROLE_PERMISSION": "vnfds:id:upload:",
392 },
393 },
394 "vnfd": {
395 "METHODS": ("GET",), # descriptor inside package
396 "ROLE_PERMISSION": "vnfds:id:content:",
397 },
398 "artifacts": {
399 "METHODS": ("GET",),
400 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
401 "*": None,
402 },
403 "action": {
404 "METHODS": ("POST",),
405 "ROLE_PERMISSION": "vnfds:id:action:",
406 },
407 },
408 },
409 "subscriptions": {
410 "TODO": ("GET", "POST"),
411 "<ID>": {"TODO": ("GET", "DELETE")},
412 },
413 "vnfpkg_op_occs": {
414 "METHODS": ("GET",),
415 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
416 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"},
417 },
418 }
419 },
420 "nslcm": {
421 "v1": {
422 "ns_instances_content": {
423 "METHODS": ("GET", "POST"),
424 "ROLE_PERMISSION": "ns_instances:",
425 "<ID>": {
426 "METHODS": ("GET", "DELETE"),
427 "ROLE_PERMISSION": "ns_instances:id:",
428 },
429 },
430 "ns_instances": {
431 "METHODS": ("GET", "POST"),
432 "ROLE_PERMISSION": "ns_instances:",
433 "<ID>": {
434 "METHODS": ("GET", "DELETE"),
435 "ROLE_PERMISSION": "ns_instances:id:",
436 "heal": {
437 "METHODS": ("POST",),
438 "ROLE_PERMISSION": "ns_instances:id:heal:",
439 },
440 "scale": {
441 "METHODS": ("POST",),
442 "ROLE_PERMISSION": "ns_instances:id:scale:",
443 },
444 "terminate": {
445 "METHODS": ("POST",),
446 "ROLE_PERMISSION": "ns_instances:id:terminate:",
447 },
448 "instantiate": {
449 "METHODS": ("POST",),
450 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
451 },
452 "migrate": {
453 "METHODS": ("POST",),
454 "ROLE_PERMISSION": "ns_instances:id:migrate:",
455 },
456 "action": {
457 "METHODS": ("POST",),
458 "ROLE_PERMISSION": "ns_instances:id:action:",
459 },
460 "update": {
461 "METHODS": ("POST",),
462 "ROLE_PERMISSION": "ns_instances:id:update:",
463 },
464 },
465 },
466 "ns_lcm_op_occs": {
467 "METHODS": ("GET",),
468 "ROLE_PERMISSION": "ns_instances:opps:",
469 "<ID>": {
470 "METHODS": ("GET",),
471 "ROLE_PERMISSION": "ns_instances:opps:id:",
472 },
473 },
474 "vnfrs": {
475 "METHODS": ("GET",),
476 "ROLE_PERMISSION": "vnf_instances:",
477 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
478 },
479 "vnf_instances": {
480 "METHODS": ("GET",),
481 "ROLE_PERMISSION": "vnf_instances:",
482 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
483 },
484 "subscriptions": {
485 "METHODS": ("GET", "POST"),
486 "ROLE_PERMISSION": "ns_subscriptions:",
487 "<ID>": {
488 "METHODS": ("GET", "DELETE"),
489 "ROLE_PERMISSION": "ns_subscriptions:id:",
490 },
491 },
492 }
493 },
494 "vnflcm": {
495 "v1": {
496 "vnf_instances": {"METHODS": ("GET", "POST"),
497 "ROLE_PERMISSION": "vnflcm_instances:",
498 "<ID>": {"METHODS": ("GET", "DELETE"),
499 "ROLE_PERMISSION": "vnflcm_instances:id:",
500 "scale": {"METHODS": ("POST",),
501 "ROLE_PERMISSION": "vnflcm_instances:id:scale:"
502 },
503 "terminate": {"METHODS": ("POST",),
504 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:"
505 },
506 "instantiate": {"METHODS": ("POST",),
507 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:"
508 },
509 }
510 },
511 "vnf_lcm_op_occs": {"METHODS": ("GET",),
512 "ROLE_PERMISSION": "vnf_instances:opps:",
513 "<ID>": {"METHODS": ("GET",),
514 "ROLE_PERMISSION": "vnf_instances:opps:id:"
515 },
516 },
517 "subscriptions": {"METHODS": ("GET", "POST"),
518 "ROLE_PERMISSION": "vnflcm_subscriptions:",
519 "<ID>": {"METHODS": ("GET", "DELETE"),
520 "ROLE_PERMISSION": "vnflcm_subscriptions:id:"
521 }
522 },
523 }
524 },
525 "nst": {
526 "v1": {
527 "netslice_templates_content": {
528 "METHODS": ("GET", "POST"),
529 "ROLE_PERMISSION": "slice_templates:",
530 "<ID>": {
531 "METHODS": ("GET", "PUT", "DELETE"),
532 "ROLE_PERMISSION": "slice_templates:id:",
533 },
534 },
535 "netslice_templates": {
536 "METHODS": ("GET", "POST"),
537 "ROLE_PERMISSION": "slice_templates:",
538 "<ID>": {
539 "METHODS": ("GET", "DELETE"),
540 "TODO": ("PATCH",),
541 "ROLE_PERMISSION": "slice_templates:id:",
542 "nst_content": {
543 "METHODS": ("GET", "PUT"),
544 "ROLE_PERMISSION": "slice_templates:id:content:",
545 },
546 "nst": {
547 "METHODS": ("GET",), # descriptor inside package
548 "ROLE_PERMISSION": "slice_templates:id:content:",
549 },
550 "artifacts": {
551 "METHODS": ("GET",),
552 "ROLE_PERMISSION": "slice_templates:id:content:",
553 "*": None,
554 },
555 },
556 },
557 "subscriptions": {
558 "TODO": ("GET", "POST"),
559 "<ID>": {"TODO": ("GET", "DELETE")},
560 },
561 }
562 },
563 "nsilcm": {
564 "v1": {
565 "netslice_instances_content": {
566 "METHODS": ("GET", "POST"),
567 "ROLE_PERMISSION": "slice_instances:",
568 "<ID>": {
569 "METHODS": ("GET", "DELETE"),
570 "ROLE_PERMISSION": "slice_instances:id:",
571 },
572 },
573 "netslice_instances": {
574 "METHODS": ("GET", "POST"),
575 "ROLE_PERMISSION": "slice_instances:",
576 "<ID>": {
577 "METHODS": ("GET", "DELETE"),
578 "ROLE_PERMISSION": "slice_instances:id:",
579 "terminate": {
580 "METHODS": ("POST",),
581 "ROLE_PERMISSION": "slice_instances:id:terminate:",
582 },
583 "instantiate": {
584 "METHODS": ("POST",),
585 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
586 },
587 "action": {
588 "METHODS": ("POST",),
589 "ROLE_PERMISSION": "slice_instances:id:action:",
590 },
591 },
592 },
593 "nsi_lcm_op_occs": {
594 "METHODS": ("GET",),
595 "ROLE_PERMISSION": "slice_instances:opps:",
596 "<ID>": {
597 "METHODS": ("GET",),
598 "ROLE_PERMISSION": "slice_instances:opps:id:",
599 },
600 },
601 }
602 },
603 "nspm": {
604 "v1": {
605 "pm_jobs": {
606 "<ID>": {
607 "reports": {
608 "<ID>": {
609 "METHODS": ("GET",),
610 "ROLE_PERMISSION": "reports:id:",
611 }
612 }
613 },
614 },
615 },
616 },
617 "nsfm": {
618 "v1": {
619 "alarms": {"METHODS": ("GET", "PATCH"),
620 "ROLE_PERMISSION": "alarms:",
621 "<ID>": {"METHODS": ("GET", "PATCH"),
622 "ROLE_PERMISSION": "alarms:id:",
623 },
624 }
625 },
626 },
627 }
628
629
630 class NbiException(Exception):
631 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
632 Exception.__init__(self, message)
633 self.http_code = http_code
634
635
636 class Server(object):
637 instance = 0
638 # to decode bytes to str
639 reader = getreader("utf-8")
640
641 def __init__(self):
642 self.instance += 1
643 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
644 self.engine = Engine(self.authenticator)
645
646 def _format_in(self, kwargs):
647 try:
648 indata = None
649 if cherrypy.request.body.length:
650 error_text = "Invalid input format "
651
652 if "Content-Type" in cherrypy.request.headers:
653 if "application/json" in cherrypy.request.headers["Content-Type"]:
654 error_text = "Invalid json format "
655 indata = json.load(self.reader(cherrypy.request.body))
656 cherrypy.request.headers.pop("Content-File-MD5", None)
657 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
658 error_text = "Invalid yaml format "
659 indata = yaml.load(
660 cherrypy.request.body, Loader=yaml.SafeLoader
661 )
662 cherrypy.request.headers.pop("Content-File-MD5", None)
663 elif (
664 "application/binary" in cherrypy.request.headers["Content-Type"]
665 or "application/gzip"
666 in cherrypy.request.headers["Content-Type"]
667 or "application/zip" in cherrypy.request.headers["Content-Type"]
668 or "text/plain" in cherrypy.request.headers["Content-Type"]
669 ):
670 indata = cherrypy.request.body # .read()
671 elif (
672 "multipart/form-data"
673 in cherrypy.request.headers["Content-Type"]
674 ):
675 if "descriptor_file" in kwargs:
676 filecontent = kwargs.pop("descriptor_file")
677 if not filecontent.file:
678 raise NbiException(
679 "empty file or content", HTTPStatus.BAD_REQUEST
680 )
681 indata = filecontent.file # .read()
682 if filecontent.content_type.value:
683 cherrypy.request.headers[
684 "Content-Type"
685 ] = filecontent.content_type.value
686 else:
687 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
688 # "Only 'Content-Type' of type 'application/json' or
689 # 'application/yaml' for input format are available")
690 error_text = "Invalid yaml format "
691 indata = yaml.load(
692 cherrypy.request.body, Loader=yaml.SafeLoader
693 )
694 cherrypy.request.headers.pop("Content-File-MD5", None)
695 else:
696 error_text = "Invalid yaml format "
697 indata = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
698 cherrypy.request.headers.pop("Content-File-MD5", None)
699 if not indata:
700 indata = {}
701
702 format_yaml = False
703 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
704 format_yaml = True
705
706 for k, v in kwargs.items():
707 if isinstance(v, str):
708 if v == "":
709 kwargs[k] = None
710 elif format_yaml:
711 try:
712 kwargs[k] = yaml.load(v, Loader=yaml.SafeLoader)
713 except Exception:
714 pass
715 elif (
716 k.endswith(".gt")
717 or k.endswith(".lt")
718 or k.endswith(".gte")
719 or k.endswith(".lte")
720 ):
721 try:
722 kwargs[k] = int(v)
723 except Exception:
724 try:
725 kwargs[k] = float(v)
726 except Exception:
727 pass
728 elif v.find(",") > 0:
729 kwargs[k] = v.split(",")
730 elif isinstance(v, (list, tuple)):
731 for index in range(0, len(v)):
732 if v[index] == "":
733 v[index] = None
734 elif format_yaml:
735 try:
736 v[index] = yaml.load(v[index], Loader=yaml.SafeLoader)
737 except Exception:
738 pass
739
740 return indata
741 except (ValueError, yaml.YAMLError) as exc:
742 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
743 except KeyError as exc:
744 raise NbiException(
745 "Query string error: " + str(exc), HTTPStatus.BAD_REQUEST
746 )
747 except Exception as exc:
748 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
749
750 @staticmethod
751 def _format_out(data, token_info=None, _format=None):
752 """
753 return string of dictionary data according to requested json, yaml, xml. By default json
754 :param data: response to be sent. Can be a dict, text or file
755 :param token_info: Contains among other username and project
756 :param _format: The format to be set as Content-Type if data is a file
757 :return: None
758 """
759 accept = cherrypy.request.headers.get("Accept")
760 if data is None:
761 if accept and "text/html" in accept:
762 return html.format(
763 data, cherrypy.request, cherrypy.response, token_info
764 )
765 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
766 return
767 elif hasattr(data, "read"): # file object
768 if _format:
769 cherrypy.response.headers["Content-Type"] = _format
770 elif "b" in data.mode: # binariy asssumig zip
771 cherrypy.response.headers["Content-Type"] = "application/zip"
772 else:
773 cherrypy.response.headers["Content-Type"] = "text/plain"
774 # TODO check that cherrypy close file. If not implement pending things to close per thread next
775 return data
776 if accept:
777 if "text/html" in accept:
778 return html.format(
779 data, cherrypy.request, cherrypy.response, token_info
780 )
781 elif "application/yaml" in accept or "*/*" in accept:
782 pass
783 elif "application/json" in accept or (
784 cherrypy.response.status and cherrypy.response.status >= 300
785 ):
786 cherrypy.response.headers[
787 "Content-Type"
788 ] = "application/json; charset=utf-8"
789 a = json.dumps(data, indent=4) + "\n"
790 return a.encode("utf8")
791 cherrypy.response.headers["Content-Type"] = "application/yaml"
792 return yaml.safe_dump(
793 data,
794 explicit_start=True,
795 indent=4,
796 default_flow_style=False,
797 tags=False,
798 encoding="utf-8",
799 allow_unicode=True,
800 ) # , canonical=True, default_style='"'
801
802 @cherrypy.expose
803 def index(self, *args, **kwargs):
804 token_info = None
805 try:
806 if cherrypy.request.method == "GET":
807 token_info = self.authenticator.authorize()
808 outdata = token_info # Home page
809 else:
810 raise cherrypy.HTTPError(
811 HTTPStatus.METHOD_NOT_ALLOWED.value,
812 "Method {} not allowed for tokens".format(cherrypy.request.method),
813 )
814
815 return self._format_out(outdata, token_info)
816
817 except (EngineException, AuthException) as e:
818 # cherrypy.log("index Exception {}".format(e))
819 cherrypy.response.status = e.http_code.value
820 return self._format_out("Welcome to OSM!", token_info)
821
822 @cherrypy.expose
823 def version(self, *args, **kwargs):
824 # TODO consider to remove and provide version using the static version file
825 try:
826 if cherrypy.request.method != "GET":
827 raise NbiException(
828 "Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED
829 )
830 elif args or kwargs:
831 raise NbiException(
832 "Invalid URL or query string for version",
833 HTTPStatus.METHOD_NOT_ALLOWED,
834 )
835 # TODO include version of other modules, pick up from some kafka admin message
836 osm_nbi_version = {"version": nbi_version, "date": nbi_version_date}
837 return self._format_out(osm_nbi_version)
838 except NbiException as e:
839 cherrypy.response.status = e.http_code.value
840 problem_details = {
841 "code": e.http_code.name,
842 "status": e.http_code.value,
843 "detail": str(e),
844 }
845 return self._format_out(problem_details, None)
846
847 def domain(self):
848 try:
849 domains = {
850 "user_domain_name": cherrypy.tree.apps["/osm"]
851 .config["authentication"]
852 .get("user_domain_name"),
853 "project_domain_name": cherrypy.tree.apps["/osm"]
854 .config["authentication"]
855 .get("project_domain_name"),
856 }
857 return self._format_out(domains)
858 except NbiException as e:
859 cherrypy.response.status = e.http_code.value
860 problem_details = {
861 "code": e.http_code.name,
862 "status": e.http_code.value,
863 "detail": str(e),
864 }
865 return self._format_out(problem_details, None)
866
867 @staticmethod
868 def _format_login(token_info):
869 """
870 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
871 log this information
872 :param token_info: Dictionary with token content
873 :return: None
874 """
875 cherrypy.request.login = token_info.get("username", "-")
876 if token_info.get("project_name"):
877 cherrypy.request.login += "/" + token_info["project_name"]
878 if token_info.get("id"):
879 cherrypy.request.login += ";session=" + token_info["id"][0:12]
880
881 # NS Fault Management
882 @cherrypy.expose
883 def nsfm(self, version=None, topic=None, uuid=None, project_name=None, ns_id=None, *args, **kwargs):
884 if topic == 'alarms':
885 try:
886 method = cherrypy.request.method
887 role_permission = self._check_valid_url_method(method, "nsfm", version, topic, None, None, *args)
888 query_string_operations = self._extract_query_string_operations(kwargs, method)
889
890 self.authenticator.authorize(role_permission, query_string_operations, None)
891
892 # to handle get request
893 if cherrypy.request.method == 'GET':
894 # if request is on basis of uuid
895 if uuid and uuid != 'None':
896 try:
897 alarm = self.engine.db.get_one("alarms", {"uuid": uuid})
898 alarm_action = self.engine.db.get_one("alarms_action", {"uuid": uuid})
899 alarm.update(alarm_action)
900 vnf = self.engine.db.get_one("vnfrs", {"nsr-id-ref": alarm["tags"]["ns_id"]})
901 alarm["vnf-id"] = vnf["_id"]
902 return self._format_out(str(alarm))
903 except Exception:
904 return self._format_out("Please provide valid alarm uuid")
905 elif ns_id and ns_id != 'None':
906 # if request is on basis of ns_id
907 try:
908 alarms = self.engine.db.get_list("alarms", {"tags.ns_id": ns_id})
909 for alarm in alarms:
910 alarm_action = self.engine.db.get_one("alarms_action", {"uuid": alarm['uuid']})
911 alarm.update(alarm_action)
912 return self._format_out(str(alarms))
913 except Exception:
914 return self._format_out("Please provide valid ns id")
915 else:
916 # to return only alarm which are related to given project
917 project = self.engine.db.get_one("projects", {"name": project_name})
918 project_id = project.get('_id')
919 ns_list = self.engine.db.get_list("nsrs", {"_admin.projects_read": project_id})
920 ns_ids = []
921 for ns in ns_list:
922 ns_ids.append(ns.get("_id"))
923 alarms = self.engine.db.get_list("alarms")
924 alarm_list = [alarm for alarm in alarms if alarm["tags"]["ns_id"] in ns_ids]
925 for alrm in alarm_list:
926 action = self.engine.db.get_one("alarms_action", {"uuid": alrm.get("uuid")})
927 alrm.update(action)
928 return self._format_out(str(alarm_list))
929 # to handle patch request for alarm update
930 elif cherrypy.request.method == 'PATCH':
931 data = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
932 try:
933 # check if uuid is valid
934 self.engine.db.get_one("alarms", {"uuid": data.get("uuid")})
935 except Exception:
936 return self._format_out("Please provide valid alarm uuid.")
937 if data.get("is_enable") is not None:
938 if data.get("is_enable"):
939 alarm_status = 'ok'
940 else:
941 alarm_status = 'disabled'
942 self.engine.db.set_one("alarms", {"uuid": data.get("uuid")},
943 {"alarm_status": alarm_status})
944 else:
945 self.engine.db.set_one("alarms", {"uuid": data.get("uuid")},
946 {"threshold": data.get("threshold")})
947 return self._format_out("Alarm updated")
948 except Exception as e:
949 cherrypy.response.status = e.http_code.value
950 if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException,
951 ValidationError, AuthconnException)):
952 http_code_value = cherrypy.response.status = e.http_code.value
953 http_code_name = e.http_code.name
954 cherrypy.log("Exception {}".format(e))
955 else:
956 http_code_value = cherrypy.response.status = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
957 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
958 http_code_name = HTTPStatus.BAD_REQUEST.name
959 problem_details = {
960 "code": http_code_name,
961 "status": http_code_value,
962 "detail": str(e),
963 }
964 return self._format_out(problem_details)
965
966 @cherrypy.expose
967 def token(self, method, token_id=None, kwargs=None):
968 token_info = None
969 # self.engine.load_dbase(cherrypy.request.app.config)
970 indata = self._format_in(kwargs)
971 if not isinstance(indata, dict):
972 raise NbiException(
973 "Expected application/yaml or application/json Content-Type",
974 HTTPStatus.BAD_REQUEST,
975 )
976
977 if method == "GET":
978 token_info = self.authenticator.authorize()
979 # for logging
980 self._format_login(token_info)
981 if token_id:
982 outdata = self.authenticator.get_token(token_info, token_id)
983 else:
984 outdata = self.authenticator.get_token_list(token_info)
985 elif method == "POST":
986 try:
987 token_info = self.authenticator.authorize()
988 except Exception:
989 token_info = None
990 if kwargs:
991 indata.update(kwargs)
992 # This is needed to log the user when authentication fails
993 cherrypy.request.login = "{}".format(indata.get("username", "-"))
994 outdata = token_info = self.authenticator.new_token(
995 token_info, indata, cherrypy.request.remote
996 )
997 cherrypy.session["Authorization"] = outdata["_id"]
998 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
999 # for logging
1000 self._format_login(token_info)
1001 # password expiry check
1002 if self.authenticator.check_password_expiry(outdata):
1003 outdata = {"id": outdata["id"],
1004 "message": "change_password",
1005 "user_id": outdata["user_id"]
1006 }
1007 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1008 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1009 elif method == "DELETE":
1010 if not token_id and "id" in kwargs:
1011 token_id = kwargs["id"]
1012 elif not token_id:
1013 token_info = self.authenticator.authorize()
1014 # for logging
1015 self._format_login(token_info)
1016 token_id = token_info["_id"]
1017 outdata = self.authenticator.del_token(token_id)
1018 token_info = None
1019 cherrypy.session["Authorization"] = "logout"
1020 # cherrypy.response.cookie["Authorization"] = token_id
1021 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1022 else:
1023 raise NbiException(
1024 "Method {} not allowed for token".format(method),
1025 HTTPStatus.METHOD_NOT_ALLOWED,
1026 )
1027 return self._format_out(outdata, token_info)
1028
1029 @cherrypy.expose
1030 def test(self, *args, **kwargs):
1031 if not cherrypy.config.get("server.enable_test") or (
1032 isinstance(cherrypy.config["server.enable_test"], str)
1033 and cherrypy.config["server.enable_test"].lower() == "false"
1034 ):
1035 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value
1036 return "test URL is disabled"
1037 thread_info = None
1038 if args and args[0] == "help":
1039 return (
1040 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1041 "sleep/<time>\nmessage/topic\n</pre></html>"
1042 )
1043
1044 elif args and args[0] == "init":
1045 try:
1046 # self.engine.load_dbase(cherrypy.request.app.config)
1047 self.engine.create_admin()
1048 return "Done. User 'admin', password 'admin' created"
1049 except Exception:
1050 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1051 return self._format_out("Database already initialized")
1052 elif args and args[0] == "file":
1053 return cherrypy.lib.static.serve_file(
1054 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1],
1055 "text/plain",
1056 "attachment",
1057 )
1058 elif args and args[0] == "file2":
1059 f_path = (
1060 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1]
1061 )
1062 f = open(f_path, "r")
1063 cherrypy.response.headers["Content-type"] = "text/plain"
1064 return f
1065
1066 elif len(args) == 2 and args[0] == "db-clear":
1067 deleted_info = self.engine.db.del_list(args[1], kwargs)
1068 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
1069 elif len(args) and args[0] == "fs-clear":
1070 if len(args) >= 2:
1071 folders = (args[1],)
1072 else:
1073 folders = self.engine.fs.dir_ls(".")
1074 for folder in folders:
1075 self.engine.fs.file_delete(folder)
1076 return ",".join(folders) + " folders deleted\n"
1077 elif args and args[0] == "login":
1078 if not cherrypy.request.headers.get("Authorization"):
1079 cherrypy.response.headers[
1080 "WWW-Authenticate"
1081 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
1082 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1083 elif args and args[0] == "login2":
1084 if not cherrypy.request.headers.get("Authorization"):
1085 cherrypy.response.headers[
1086 "WWW-Authenticate"
1087 ] = 'Bearer realm="Access to OSM site"'
1088 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1089 elif args and args[0] == "sleep":
1090 sleep_time = 5
1091 try:
1092 sleep_time = int(args[1])
1093 except Exception:
1094 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1095 return self._format_out("Database already initialized")
1096 thread_info = cherrypy.thread_data
1097 print(thread_info)
1098 time.sleep(sleep_time)
1099 # thread_info
1100 elif len(args) >= 2 and args[0] == "message":
1101 main_topic = args[1]
1102 return_text = "<html><pre>{} ->\n".format(main_topic)
1103 try:
1104 if cherrypy.request.method == "POST":
1105 to_send = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
1106 for k, v in to_send.items():
1107 self.engine.msg.write(main_topic, k, v)
1108 return_text += " {}: {}\n".format(k, v)
1109 elif cherrypy.request.method == "GET":
1110 for k, v in kwargs.items():
1111 v_dict = yaml.load(v, Loader=yaml.SafeLoader)
1112 self.engine.msg.write(main_topic, k, v_dict)
1113 return_text += " {}: {}\n".format(k, v_dict)
1114 except Exception as e:
1115 return_text += "Error: " + str(e)
1116 return_text += "</pre></html>\n"
1117 return return_text
1118
1119 return_text = (
1120 "<html><pre>\nheaders:\n args: {}\n".format(args)
1121 + " kwargs: {}\n".format(kwargs)
1122 + " headers: {}\n".format(cherrypy.request.headers)
1123 + " path_info: {}\n".format(cherrypy.request.path_info)
1124 + " query_string: {}\n".format(cherrypy.request.query_string)
1125 + " session: {}\n".format(cherrypy.session)
1126 + " cookie: {}\n".format(cherrypy.request.cookie)
1127 + " method: {}\n".format(cherrypy.request.method)
1128 + " session: {}\n".format(cherrypy.session.get("fieldname"))
1129 + " body:\n"
1130 )
1131 return_text += " length: {}\n".format(cherrypy.request.body.length)
1132 if cherrypy.request.body.length:
1133 return_text += " content: {}\n".format(
1134 str(
1135 cherrypy.request.body.read(
1136 int(cherrypy.request.headers.get("Content-Length", 0))
1137 )
1138 )
1139 )
1140 if thread_info:
1141 return_text += "thread: {}\n".format(thread_info)
1142 return_text += "</pre></html>"
1143 return return_text
1144
1145 @staticmethod
1146 def _check_valid_url_method(method, *args):
1147 if len(args) < 3:
1148 raise NbiException(
1149 "URL must contain at least 'main_topic/version/topic'",
1150 HTTPStatus.METHOD_NOT_ALLOWED,
1151 )
1152
1153 reference = valid_url_methods
1154 for arg in args:
1155 if arg is None:
1156 break
1157 if not isinstance(reference, dict):
1158 raise NbiException(
1159 "URL contains unexpected extra items '{}'".format(arg),
1160 HTTPStatus.METHOD_NOT_ALLOWED,
1161 )
1162
1163 if arg in reference:
1164 reference = reference[arg]
1165 elif "<ID>" in reference:
1166 reference = reference["<ID>"]
1167 elif "*" in reference:
1168 # if there is content
1169 if reference["*"]:
1170 reference = reference["*"]
1171 break
1172 else:
1173 raise NbiException(
1174 "Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED
1175 )
1176 if "TODO" in reference and method in reference["TODO"]:
1177 raise NbiException(
1178 "Method {} not supported yet for this URL".format(method),
1179 HTTPStatus.NOT_IMPLEMENTED,
1180 )
1181 elif "METHODS" in reference and method not in reference["METHODS"]:
1182 raise NbiException(
1183 "Method {} not supported for this URL".format(method),
1184 HTTPStatus.METHOD_NOT_ALLOWED,
1185 )
1186 return reference["ROLE_PERMISSION"] + method.lower()
1187
1188 @staticmethod
1189 def _set_location_header(main_topic, version, topic, id):
1190 """
1191 Insert response header Location with the URL of created item base on URL params
1192 :param main_topic:
1193 :param version:
1194 :param topic:
1195 :param id:
1196 :return: None
1197 """
1198 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1199 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(
1200 main_topic, version, topic, id
1201 )
1202 return
1203
1204 @staticmethod
1205 def _extract_query_string_operations(kwargs, method):
1206 """
1207
1208 :param kwargs:
1209 :return:
1210 """
1211 query_string_operations = []
1212 if kwargs:
1213 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1214 if qs in kwargs and kwargs[qs].lower() != "false":
1215 query_string_operations.append(qs.lower() + ":" + method.lower())
1216 return query_string_operations
1217
1218 @staticmethod
1219 def _manage_admin_query(token_info, kwargs, method, _id):
1220 """
1221 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1222 Check that users has rights to use them and returs the admin_query
1223 :param token_info: token_info rights obtained by token
1224 :param kwargs: query string input.
1225 :param method: http method: GET, POSST, PUT, ...
1226 :param _id:
1227 :return: admin_query dictionary with keys:
1228 public: True, False or None
1229 force: True or False
1230 project_id: tuple with projects used for accessing an element
1231 set_project: tuple with projects that a created element will belong to
1232 method: show, list, delete, write
1233 """
1234 admin_query = {
1235 "force": False,
1236 "project_id": (token_info["project_id"],),
1237 "username": token_info["username"],
1238 "admin": token_info["admin"],
1239 "public": None,
1240 "allow_show_user_project_role": token_info["allow_show_user_project_role"],
1241 }
1242 if kwargs:
1243 # FORCE
1244 if "FORCE" in kwargs:
1245 if (
1246 kwargs["FORCE"].lower() != "false"
1247 ): # if None or True set force to True
1248 admin_query["force"] = True
1249 del kwargs["FORCE"]
1250 # PUBLIC
1251 if "PUBLIC" in kwargs:
1252 if (
1253 kwargs["PUBLIC"].lower() != "false"
1254 ): # if None or True set public to True
1255 admin_query["public"] = True
1256 else:
1257 admin_query["public"] = False
1258 del kwargs["PUBLIC"]
1259 # ADMIN
1260 if "ADMIN" in kwargs:
1261 behave_as = kwargs.pop("ADMIN")
1262 if behave_as.lower() != "false":
1263 if not token_info["admin"]:
1264 raise NbiException(
1265 "Only admin projects can use 'ADMIN' query string",
1266 HTTPStatus.UNAUTHORIZED,
1267 )
1268 if (
1269 not behave_as or behave_as.lower() == "true"
1270 ): # convert True, None to empty list
1271 admin_query["project_id"] = ()
1272 elif isinstance(behave_as, (list, tuple)):
1273 admin_query["project_id"] = behave_as
1274 else: # isinstance(behave_as, str)
1275 admin_query["project_id"] = (behave_as,)
1276 if "SET_PROJECT" in kwargs:
1277 set_project = kwargs.pop("SET_PROJECT")
1278 if not set_project:
1279 admin_query["set_project"] = list(admin_query["project_id"])
1280 else:
1281 if isinstance(set_project, str):
1282 set_project = (set_project,)
1283 if admin_query["project_id"]:
1284 for p in set_project:
1285 if p not in admin_query["project_id"]:
1286 raise NbiException(
1287 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1288 "'ADMIN='{p}'".format(p=p),
1289 HTTPStatus.UNAUTHORIZED,
1290 )
1291 admin_query["set_project"] = set_project
1292
1293 # PROJECT_READ
1294 # if "PROJECT_READ" in kwargs:
1295 # admin_query["project"] = kwargs.pop("project")
1296 # if admin_query["project"] == token_info["project_id"]:
1297 if method == "GET":
1298 if _id:
1299 admin_query["method"] = "show"
1300 else:
1301 admin_query["method"] = "list"
1302 elif method == "DELETE":
1303 admin_query["method"] = "delete"
1304 else:
1305 admin_query["method"] = "write"
1306 return admin_query
1307
1308 @cherrypy.expose
1309 def default(
1310 self,
1311 main_topic=None,
1312 version=None,
1313 topic=None,
1314 _id=None,
1315 item=None,
1316 *args,
1317 **kwargs
1318 ):
1319 token_info = None
1320 outdata = None
1321 _format = None
1322 method = "DONE"
1323 engine_topic = None
1324 rollback = []
1325 engine_session = None
1326 try:
1327 if not main_topic or not version or not topic:
1328 raise NbiException(
1329 "URL must contain at least 'main_topic/version/topic'",
1330 HTTPStatus.METHOD_NOT_ALLOWED,
1331 )
1332 if main_topic not in (
1333 "admin",
1334 "vnfpkgm",
1335 "nsd",
1336 "nslcm",
1337 "pdu",
1338 "nst",
1339 "nsilcm",
1340 "nspm",
1341 "vnflcm",
1342 ):
1343 raise NbiException(
1344 "URL main_topic '{}' not supported".format(main_topic),
1345 HTTPStatus.METHOD_NOT_ALLOWED,
1346 )
1347 if version != "v1":
1348 raise NbiException(
1349 "URL version '{}' not supported".format(version),
1350 HTTPStatus.METHOD_NOT_ALLOWED,
1351 )
1352
1353 if (
1354 kwargs
1355 and "METHOD" in kwargs
1356 and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1357 ):
1358 method = kwargs.pop("METHOD")
1359 else:
1360 method = cherrypy.request.method
1361
1362 role_permission = self._check_valid_url_method(
1363 method, main_topic, version, topic, _id, item, *args
1364 )
1365 query_string_operations = self._extract_query_string_operations(
1366 kwargs, method
1367 )
1368 if main_topic == "admin" and topic == "tokens":
1369 return self.token(method, _id, kwargs)
1370 token_info = self.authenticator.authorize(
1371 role_permission, query_string_operations, _id
1372 )
1373 if main_topic == "admin" and topic == "domains":
1374 return self.domain()
1375 engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
1376 indata = self._format_in(kwargs)
1377 engine_topic = topic
1378
1379 if item and topic != "pm_jobs":
1380 engine_topic = item
1381
1382 if main_topic == "nsd":
1383 engine_topic = "nsds"
1384 elif main_topic == "vnfpkgm":
1385 engine_topic = "vnfds"
1386 if topic == "vnfpkg_op_occs":
1387 engine_topic = "vnfpkgops"
1388 if topic == "vnf_packages" and item == "action":
1389 engine_topic = "vnfpkgops"
1390 elif main_topic == "nslcm":
1391 engine_topic = "nsrs"
1392 if topic == "ns_lcm_op_occs":
1393 engine_topic = "nslcmops"
1394 if topic == "vnfrs" or topic == "vnf_instances":
1395 engine_topic = "vnfrs"
1396 elif main_topic == "vnflcm":
1397 if topic == "vnf_lcm_op_occs":
1398 engine_topic = "vnflcmops"
1399 elif main_topic == "nst":
1400 engine_topic = "nsts"
1401 elif main_topic == "nsilcm":
1402 engine_topic = "nsis"
1403 if topic == "nsi_lcm_op_occs":
1404 engine_topic = "nsilcmops"
1405 elif main_topic == "pdu":
1406 engine_topic = "pdus"
1407 if (
1408 engine_topic == "vims"
1409 ): # TODO this is for backward compatibility, it will be removed in the future
1410 engine_topic = "vim_accounts"
1411
1412 if topic == "subscriptions":
1413 engine_topic = main_topic + "_" + topic
1414
1415 if method == "GET":
1416 if item in (
1417 "nsd_content",
1418 "package_content",
1419 "artifacts",
1420 "vnfd",
1421 "nsd",
1422 "nst",
1423 "nst_content",
1424 ):
1425 if item in ("vnfd", "nsd", "nst"):
1426 path = "$DESCRIPTOR"
1427 elif args:
1428 path = args
1429 elif item == "artifacts":
1430 path = ()
1431 else:
1432 path = None
1433 file, _format = self.engine.get_file(
1434 engine_session,
1435 engine_topic,
1436 _id,
1437 path,
1438 cherrypy.request.headers.get("Accept"),
1439 )
1440 outdata = file
1441 elif not _id:
1442 outdata = self.engine.get_item_list(
1443 engine_session, engine_topic, kwargs, api_req=True
1444 )
1445 else:
1446 if item == "reports":
1447 # TODO check that project_id (_id in this context) has permissions
1448 _id = args[0]
1449 filter_q = None
1450 if "vcaStatusRefresh" in kwargs:
1451 filter_q = {"vcaStatusRefresh": kwargs["vcaStatusRefresh"]}
1452 outdata = self.engine.get_item(engine_session, engine_topic, _id, filter_q, True)
1453
1454 elif method == "POST":
1455 cherrypy.response.status = HTTPStatus.CREATED.value
1456 if topic in (
1457 "ns_descriptors_content",
1458 "vnf_packages_content",
1459 "netslice_templates_content",
1460 ):
1461 _id = cherrypy.request.headers.get("Transaction-Id")
1462 if not _id:
1463 _id, _ = self.engine.new_item(
1464 rollback,
1465 engine_session,
1466 engine_topic,
1467 {},
1468 None,
1469 cherrypy.request.headers,
1470 )
1471 completed = self.engine.upload_content(
1472 engine_session,
1473 engine_topic,
1474 _id,
1475 indata,
1476 kwargs,
1477 cherrypy.request.headers,
1478 )
1479 if completed:
1480 self._set_location_header(main_topic, version, topic, _id)
1481 else:
1482 cherrypy.response.headers["Transaction-Id"] = _id
1483 outdata = {"id": _id}
1484 elif topic == "ns_instances_content":
1485 # creates NSR
1486 _id, _ = self.engine.new_item(
1487 rollback, engine_session, engine_topic, indata, kwargs
1488 )
1489 # creates nslcmop
1490 indata["lcmOperationType"] = "instantiate"
1491 indata["nsInstanceId"] = _id
1492 nslcmop_id, _ = self.engine.new_item(
1493 rollback, engine_session, "nslcmops", indata, None
1494 )
1495 self._set_location_header(main_topic, version, topic, _id)
1496 outdata = {"id": _id, "nslcmop_id": nslcmop_id}
1497 elif topic == "ns_instances" and item:
1498 indata["lcmOperationType"] = item
1499 indata["nsInstanceId"] = _id
1500 _id, _ = self.engine.new_item(
1501 rollback, engine_session, "nslcmops", indata, kwargs
1502 )
1503 self._set_location_header(
1504 main_topic, version, "ns_lcm_op_occs", _id
1505 )
1506 outdata = {"id": _id}
1507 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1508 elif topic == "netslice_instances_content":
1509 # creates NetSlice_Instance_record (NSIR)
1510 _id, _ = self.engine.new_item(
1511 rollback, engine_session, engine_topic, indata, kwargs
1512 )
1513 self._set_location_header(main_topic, version, topic, _id)
1514 indata["lcmOperationType"] = "instantiate"
1515 indata["netsliceInstanceId"] = _id
1516 nsilcmop_id, _ = self.engine.new_item(
1517 rollback, engine_session, "nsilcmops", indata, kwargs
1518 )
1519 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
1520 elif topic == "netslice_instances" and item:
1521 indata["lcmOperationType"] = item
1522 indata["netsliceInstanceId"] = _id
1523 _id, _ = self.engine.new_item(
1524 rollback, engine_session, "nsilcmops", indata, kwargs
1525 )
1526 self._set_location_header(
1527 main_topic, version, "nsi_lcm_op_occs", _id
1528 )
1529 outdata = {"id": _id}
1530 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1531 elif topic == "vnf_packages" and item == "action":
1532 indata["lcmOperationType"] = item
1533 indata["vnfPkgId"] = _id
1534 _id, _ = self.engine.new_item(
1535 rollback, engine_session, "vnfpkgops", indata, kwargs
1536 )
1537 self._set_location_header(
1538 main_topic, version, "vnfpkg_op_occs", _id
1539 )
1540 outdata = {"id": _id}
1541 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1542 elif topic == "subscriptions":
1543 _id, _ = self.engine.new_item(
1544 rollback, engine_session, engine_topic, indata, kwargs
1545 )
1546 self._set_location_header(main_topic, version, topic, _id)
1547 link = {}
1548 link["self"] = cherrypy.response.headers["Location"]
1549 outdata = {
1550 "id": _id,
1551 "filter": indata["filter"],
1552 "callbackUri": indata["CallbackUri"],
1553 "_links": link,
1554 }
1555 cherrypy.response.status = HTTPStatus.CREATED.value
1556 elif topic == "vnf_instances" and item:
1557 indata["lcmOperationType"] = item
1558 indata["vnfInstanceId"] = _id
1559 _id, _ = self.engine.new_item(rollback, engine_session, "vnflcmops", indata, kwargs)
1560 self._set_location_header(main_topic, version, "vnf_lcm_op_occs", _id)
1561 outdata = {"id": _id}
1562 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1563 else:
1564 _id, op_id = self.engine.new_item(
1565 rollback,
1566 engine_session,
1567 engine_topic,
1568 indata,
1569 kwargs,
1570 cherrypy.request.headers,
1571 )
1572 self._set_location_header(main_topic, version, topic, _id)
1573 outdata = {"id": _id}
1574 if op_id:
1575 outdata["op_id"] = op_id
1576 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1577 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1578
1579 elif method == "DELETE":
1580 if not _id:
1581 outdata = self.engine.del_item_list(
1582 engine_session, engine_topic, kwargs
1583 )
1584 cherrypy.response.status = HTTPStatus.OK.value
1585 else: # len(args) > 1
1586 # for NS NSI generate an operation
1587 op_id = None
1588 if topic == "ns_instances_content" and not engine_session["force"]:
1589 nslcmop_desc = {
1590 "lcmOperationType": "terminate",
1591 "nsInstanceId": _id,
1592 "autoremove": True,
1593 }
1594 op_id, _ = self.engine.new_item(
1595 rollback, engine_session, "nslcmops", nslcmop_desc, kwargs
1596 )
1597 if op_id:
1598 outdata = {"_id": op_id}
1599 elif (
1600 topic == "netslice_instances_content"
1601 and not engine_session["force"]
1602 ):
1603 nsilcmop_desc = {
1604 "lcmOperationType": "terminate",
1605 "netsliceInstanceId": _id,
1606 "autoremove": True,
1607 }
1608 op_id, _ = self.engine.new_item(
1609 rollback, engine_session, "nsilcmops", nsilcmop_desc, None
1610 )
1611 if op_id:
1612 outdata = {"_id": op_id}
1613 # if there is not any deletion in process, delete
1614 if not op_id:
1615 op_id = self.engine.del_item(engine_session, engine_topic, _id)
1616 if op_id:
1617 outdata = {"op_id": op_id}
1618 cherrypy.response.status = (
1619 HTTPStatus.ACCEPTED.value
1620 if op_id
1621 else HTTPStatus.NO_CONTENT.value
1622 )
1623
1624 elif method in ("PUT", "PATCH"):
1625 op_id = None
1626 if not indata and not kwargs and not engine_session.get("set_project"):
1627 raise NbiException(
1628 "Nothing to update. Provide payload and/or query string",
1629 HTTPStatus.BAD_REQUEST,
1630 )
1631 if (
1632 item in ("nsd_content", "package_content", "nst_content")
1633 and method == "PUT"
1634 ):
1635 completed = self.engine.upload_content(
1636 engine_session,
1637 engine_topic,
1638 _id,
1639 indata,
1640 kwargs,
1641 cherrypy.request.headers,
1642 )
1643 if not completed:
1644 cherrypy.response.headers["Transaction-Id"] = id
1645 else:
1646 op_id = self.engine.edit_item(
1647 engine_session, engine_topic, _id, indata, kwargs
1648 )
1649
1650 if op_id:
1651 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1652 outdata = {"op_id": op_id}
1653 else:
1654 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
1655 outdata = None
1656 else:
1657 raise NbiException(
1658 "Method {} not allowed".format(method),
1659 HTTPStatus.METHOD_NOT_ALLOWED,
1660 )
1661
1662 # if Role information changes, it is needed to reload the information of roles
1663 if topic == "roles" and method != "GET":
1664 self.authenticator.load_operation_to_allowed_roles()
1665
1666 if (
1667 topic == "projects"
1668 and method == "DELETE"
1669 or topic in ["users", "roles"]
1670 and method in ["PUT", "PATCH", "DELETE"]
1671 ):
1672 self.authenticator.remove_token_from_cache()
1673
1674 return self._format_out(outdata, token_info, _format)
1675 except Exception as e:
1676 if isinstance(
1677 e,
1678 (
1679 NbiException,
1680 EngineException,
1681 DbException,
1682 FsException,
1683 MsgException,
1684 AuthException,
1685 ValidationError,
1686 AuthconnException,
1687 ),
1688 ):
1689 http_code_value = cherrypy.response.status = e.http_code.value
1690 http_code_name = e.http_code.name
1691 cherrypy.log("Exception {}".format(e))
1692 else:
1693 http_code_value = (
1694 cherrypy.response.status
1695 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
1696 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
1697 http_code_name = HTTPStatus.BAD_REQUEST.name
1698 if hasattr(outdata, "close"): # is an open file
1699 outdata.close()
1700 error_text = str(e)
1701 rollback.reverse()
1702 for rollback_item in rollback:
1703 try:
1704 if rollback_item.get("operation") == "set":
1705 self.engine.db.set_one(
1706 rollback_item["topic"],
1707 {"_id": rollback_item["_id"]},
1708 rollback_item["content"],
1709 fail_on_empty=False,
1710 )
1711 elif rollback_item.get("operation") == "del_list":
1712 self.engine.db.del_list(
1713 rollback_item["topic"],
1714 rollback_item["filter"],
1715 fail_on_empty=False,
1716 )
1717 else:
1718 self.engine.db.del_one(
1719 rollback_item["topic"],
1720 {"_id": rollback_item["_id"]},
1721 fail_on_empty=False,
1722 )
1723 except Exception as e2:
1724 rollback_error_text = "Rollback Exception {}: {}".format(
1725 rollback_item, e2
1726 )
1727 cherrypy.log(rollback_error_text)
1728 error_text += ". " + rollback_error_text
1729 # if isinstance(e, MsgException):
1730 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1731 # engine_topic[:-1], method, error_text)
1732 problem_details = {
1733 "code": http_code_name,
1734 "status": http_code_value,
1735 "detail": error_text,
1736 }
1737 return self._format_out(problem_details, token_info)
1738 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1739 finally:
1740 if token_info:
1741 self._format_login(token_info)
1742 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
1743 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1744 if outdata.get(logging_id):
1745 cherrypy.request.login += ";{}={}".format(
1746 logging_id, outdata[logging_id][:36]
1747 )
1748
1749
1750 def _start_service():
1751 """
1752 Callback function called when cherrypy.engine starts
1753 Override configuration with env variables
1754 Set database, storage, message configuration
1755 Init database with admin/admin user password
1756 """
1757 global nbi_server
1758 global subscription_thread
1759 cherrypy.log.error("Starting osm_nbi")
1760 # update general cherrypy configuration
1761 update_dict = {}
1762
1763 engine_config = cherrypy.tree.apps["/osm"].config
1764 for k, v in environ.items():
1765 if not k.startswith("OSMNBI_"):
1766 continue
1767 k1, _, k2 = k[7:].lower().partition("_")
1768 if not k2:
1769 continue
1770 try:
1771 # update static configuration
1772 if k == "OSMNBI_STATIC_DIR":
1773 engine_config["/static"]["tools.staticdir.dir"] = v
1774 engine_config["/static"]["tools.staticdir.on"] = True
1775 elif k == "OSMNBI_SOCKET_PORT" or k == "OSMNBI_SERVER_PORT":
1776 update_dict["server.socket_port"] = int(v)
1777 elif k == "OSMNBI_SOCKET_HOST" or k == "OSMNBI_SERVER_HOST":
1778 update_dict["server.socket_host"] = v
1779 elif k1 in ("server", "test", "auth", "log"):
1780 update_dict[k1 + "." + k2] = v
1781 elif k1 in ("message", "database", "storage", "authentication"):
1782 # k2 = k2.replace('_', '.')
1783 if k2 in ("port", "db_port"):
1784 engine_config[k1][k2] = int(v)
1785 else:
1786 engine_config[k1][k2] = v
1787
1788 except ValueError as e:
1789 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1790 except Exception as e:
1791 cherrypy.log.warn("skipping environ '{}' on exception '{}'".format(k, e))
1792
1793 if update_dict:
1794 cherrypy.config.update(update_dict)
1795 engine_config["global"].update(update_dict)
1796
1797 # logging cherrypy
1798 log_format_simple = (
1799 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1800 )
1801 log_formatter_simple = logging.Formatter(
1802 log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S"
1803 )
1804 logger_server = logging.getLogger("cherrypy.error")
1805 logger_access = logging.getLogger("cherrypy.access")
1806 logger_cherry = logging.getLogger("cherrypy")
1807 logger_nbi = logging.getLogger("nbi")
1808
1809 if "log.file" in engine_config["global"]:
1810 file_handler = logging.handlers.RotatingFileHandler(
1811 engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0
1812 )
1813 file_handler.setFormatter(log_formatter_simple)
1814 logger_cherry.addHandler(file_handler)
1815 logger_nbi.addHandler(file_handler)
1816 # log always to standard output
1817 for format_, logger in {
1818 "nbi.server %(filename)s:%(lineno)s": logger_server,
1819 "nbi.access %(filename)s:%(lineno)s": logger_access,
1820 "%(name)s %(filename)s:%(lineno)s": logger_nbi,
1821 }.items():
1822 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
1823 log_formatter_cherry = logging.Formatter(
1824 log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S"
1825 )
1826 str_handler = logging.StreamHandler()
1827 str_handler.setFormatter(log_formatter_cherry)
1828 logger.addHandler(str_handler)
1829
1830 if engine_config["global"].get("log.level"):
1831 logger_cherry.setLevel(engine_config["global"]["log.level"])
1832 logger_nbi.setLevel(engine_config["global"]["log.level"])
1833
1834 # logging other modules
1835 for k1, logname in {
1836 "message": "nbi.msg",
1837 "database": "nbi.db",
1838 "storage": "nbi.fs",
1839 }.items():
1840 engine_config[k1]["logger_name"] = logname
1841 logger_module = logging.getLogger(logname)
1842 if "logfile" in engine_config[k1]:
1843 file_handler = logging.handlers.RotatingFileHandler(
1844 engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0
1845 )
1846 file_handler.setFormatter(log_formatter_simple)
1847 logger_module.addHandler(file_handler)
1848 if "loglevel" in engine_config[k1]:
1849 logger_module.setLevel(engine_config[k1]["loglevel"])
1850 # TODO add more entries, e.g.: storage
1851 cherrypy.tree.apps["/osm"].root.engine.start(engine_config)
1852 cherrypy.tree.apps["/osm"].root.authenticator.start(engine_config)
1853 cherrypy.tree.apps["/osm"].root.engine.init_db(target_version=database_version)
1854 cherrypy.tree.apps["/osm"].root.authenticator.init_db(
1855 target_version=auth_database_version
1856 )
1857
1858 # start subscriptions thread:
1859 subscription_thread = SubscriptionThread(
1860 config=engine_config, engine=nbi_server.engine
1861 )
1862 subscription_thread.start()
1863 # Do not capture except SubscriptionException
1864
1865 backend = engine_config["authentication"]["backend"]
1866 cherrypy.log.error(
1867 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1868 nbi_version, nbi_version_date, backend
1869 )
1870 )
1871
1872
1873 def _stop_service():
1874 """
1875 Callback function called when cherrypy.engine stops
1876 TODO: Ending database connections.
1877 """
1878 global subscription_thread
1879 if subscription_thread:
1880 subscription_thread.terminate()
1881 subscription_thread = None
1882 cherrypy.tree.apps["/osm"].root.engine.stop()
1883 cherrypy.log.error("Stopping osm_nbi")
1884
1885
1886 def nbi(config_file):
1887 global nbi_server
1888 # conf = {
1889 # '/': {
1890 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1891 # 'tools.sessions.on': True,
1892 # 'tools.response_headers.on': True,
1893 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1894 # }
1895 # }
1896 # cherrypy.Server.ssl_module = 'builtin'
1897 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1898 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1899 # cherrypy.Server.thread_pool = 10
1900 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1901
1902 # cherrypy.config.update({'tools.auth_basic.on': True,
1903 # 'tools.auth_basic.realm': 'localhost',
1904 # 'tools.auth_basic.checkpassword': validate_password})
1905 nbi_server = Server()
1906 cherrypy.engine.subscribe("start", _start_service)
1907 cherrypy.engine.subscribe("stop", _stop_service)
1908 cherrypy.quickstart(nbi_server, "/osm", config_file)
1909
1910
1911 def usage():
1912 print(
1913 """Usage: {} [options]
1914 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1915 -h|--help: shows this help
1916 """.format(
1917 sys.argv[0]
1918 )
1919 )
1920 # --log-socket-host HOST: send logs to this host")
1921 # --log-socket-port PORT: send logs using this port (default: 9022)")
1922
1923
1924 if __name__ == "__main__":
1925 try:
1926 # load parameters and configuration
1927 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
1928 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1929 config_file = None
1930 for o, a in opts:
1931 if o in ("-h", "--help"):
1932 usage()
1933 sys.exit()
1934 elif o in ("-c", "--config"):
1935 config_file = a
1936 # elif o == "--log-socket-port":
1937 # log_socket_port = a
1938 # elif o == "--log-socket-host":
1939 # log_socket_host = a
1940 # elif o == "--log-file":
1941 # log_file = a
1942 else:
1943 assert False, "Unhandled option"
1944 if config_file:
1945 if not path.isfile(config_file):
1946 print(
1947 "configuration file '{}' that not exist".format(config_file),
1948 file=sys.stderr,
1949 )
1950 exit(1)
1951 else:
1952 for config_file in (
1953 __file__[: __file__.rfind(".")] + ".cfg",
1954 "./nbi.cfg",
1955 "/etc/osm/nbi.cfg",
1956 ):
1957 if path.isfile(config_file):
1958 break
1959 else:
1960 print(
1961 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
1962 file=sys.stderr,
1963 )
1964 exit(1)
1965 nbi(config_file)
1966 except getopt.GetoptError as e:
1967 print(str(e), file=sys.stderr)
1968 # usage()
1969 exit(1)