Replace yaml.load by yaml.safe_load
[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 "verticalscale": {
465 "METHODS": ("POST",),
466 "ROLE_PERMISSION": "ns_instances:id:verticalscale:",
467 },
468 },
469 },
470 "ns_lcm_op_occs": {
471 "METHODS": ("GET",),
472 "ROLE_PERMISSION": "ns_instances:opps:",
473 "<ID>": {
474 "METHODS": ("GET",),
475 "ROLE_PERMISSION": "ns_instances:opps:id:",
476 },
477 },
478 "vnfrs": {
479 "METHODS": ("GET",),
480 "ROLE_PERMISSION": "vnf_instances:",
481 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
482 },
483 "vnf_instances": {
484 "METHODS": ("GET",),
485 "ROLE_PERMISSION": "vnf_instances:",
486 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
487 },
488 "subscriptions": {
489 "METHODS": ("GET", "POST"),
490 "ROLE_PERMISSION": "ns_subscriptions:",
491 "<ID>": {
492 "METHODS": ("GET", "DELETE"),
493 "ROLE_PERMISSION": "ns_subscriptions:id:",
494 },
495 },
496 }
497 },
498 "vnflcm": {
499 "v1": {
500 "vnf_instances": {
501 "METHODS": ("GET", "POST"),
502 "ROLE_PERMISSION": "vnflcm_instances:",
503 "<ID>": {
504 "METHODS": ("GET", "DELETE"),
505 "ROLE_PERMISSION": "vnflcm_instances:id:",
506 "scale": {
507 "METHODS": ("POST",),
508 "ROLE_PERMISSION": "vnflcm_instances:id:scale:",
509 },
510 "terminate": {
511 "METHODS": ("POST",),
512 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:",
513 },
514 "instantiate": {
515 "METHODS": ("POST",),
516 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:",
517 },
518 },
519 },
520 "vnf_lcm_op_occs": {
521 "METHODS": ("GET",),
522 "ROLE_PERMISSION": "vnf_instances:opps:",
523 "<ID>": {
524 "METHODS": ("GET",),
525 "ROLE_PERMISSION": "vnf_instances:opps:id:",
526 },
527 },
528 "subscriptions": {
529 "METHODS": ("GET", "POST"),
530 "ROLE_PERMISSION": "vnflcm_subscriptions:",
531 "<ID>": {
532 "METHODS": ("GET", "DELETE"),
533 "ROLE_PERMISSION": "vnflcm_subscriptions:id:",
534 },
535 },
536 }
537 },
538 "nst": {
539 "v1": {
540 "netslice_templates_content": {
541 "METHODS": ("GET", "POST"),
542 "ROLE_PERMISSION": "slice_templates:",
543 "<ID>": {
544 "METHODS": ("GET", "PUT", "DELETE"),
545 "ROLE_PERMISSION": "slice_templates:id:",
546 },
547 },
548 "netslice_templates": {
549 "METHODS": ("GET", "POST"),
550 "ROLE_PERMISSION": "slice_templates:",
551 "<ID>": {
552 "METHODS": ("GET", "DELETE"),
553 "TODO": ("PATCH",),
554 "ROLE_PERMISSION": "slice_templates:id:",
555 "nst_content": {
556 "METHODS": ("GET", "PUT"),
557 "ROLE_PERMISSION": "slice_templates:id:content:",
558 },
559 "nst": {
560 "METHODS": ("GET",), # descriptor inside package
561 "ROLE_PERMISSION": "slice_templates:id:content:",
562 },
563 "artifacts": {
564 "METHODS": ("GET",),
565 "ROLE_PERMISSION": "slice_templates:id:content:",
566 "*": None,
567 },
568 },
569 },
570 "subscriptions": {
571 "TODO": ("GET", "POST"),
572 "<ID>": {"TODO": ("GET", "DELETE")},
573 },
574 }
575 },
576 "nsilcm": {
577 "v1": {
578 "netslice_instances_content": {
579 "METHODS": ("GET", "POST"),
580 "ROLE_PERMISSION": "slice_instances:",
581 "<ID>": {
582 "METHODS": ("GET", "DELETE"),
583 "ROLE_PERMISSION": "slice_instances:id:",
584 },
585 },
586 "netslice_instances": {
587 "METHODS": ("GET", "POST"),
588 "ROLE_PERMISSION": "slice_instances:",
589 "<ID>": {
590 "METHODS": ("GET", "DELETE"),
591 "ROLE_PERMISSION": "slice_instances:id:",
592 "terminate": {
593 "METHODS": ("POST",),
594 "ROLE_PERMISSION": "slice_instances:id:terminate:",
595 },
596 "instantiate": {
597 "METHODS": ("POST",),
598 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
599 },
600 "action": {
601 "METHODS": ("POST",),
602 "ROLE_PERMISSION": "slice_instances:id:action:",
603 },
604 },
605 },
606 "nsi_lcm_op_occs": {
607 "METHODS": ("GET",),
608 "ROLE_PERMISSION": "slice_instances:opps:",
609 "<ID>": {
610 "METHODS": ("GET",),
611 "ROLE_PERMISSION": "slice_instances:opps:id:",
612 },
613 },
614 }
615 },
616 "nspm": {
617 "v1": {
618 "pm_jobs": {
619 "<ID>": {
620 "reports": {
621 "<ID>": {
622 "METHODS": ("GET",),
623 "ROLE_PERMISSION": "reports:id:",
624 }
625 }
626 },
627 },
628 },
629 },
630 "nsfm": {
631 "v1": {
632 "alarms": {
633 "METHODS": ("GET", "PATCH"),
634 "ROLE_PERMISSION": "alarms:",
635 "<ID>": {
636 "METHODS": ("GET", "PATCH"),
637 "ROLE_PERMISSION": "alarms:id:",
638 },
639 }
640 },
641 },
642 }
643
644
645 class NbiException(Exception):
646 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
647 Exception.__init__(self, message)
648 self.http_code = http_code
649
650
651 class Server(object):
652 instance = 0
653 # to decode bytes to str
654 reader = getreader("utf-8")
655
656 def __init__(self):
657 self.instance += 1
658 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
659 self.engine = Engine(self.authenticator)
660
661 def _format_in(self, kwargs):
662 error_text = "" # error_text must be initialized outside try
663 try:
664 indata = None
665 if cherrypy.request.body.length:
666 error_text = "Invalid input format "
667
668 if "Content-Type" in cherrypy.request.headers:
669 if "application/json" in cherrypy.request.headers["Content-Type"]:
670 error_text = "Invalid json format "
671 indata = json.load(self.reader(cherrypy.request.body))
672 cherrypy.request.headers.pop("Content-File-MD5", None)
673 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
674 error_text = "Invalid yaml format "
675 indata = yaml.safe_load(cherrypy.request.body)
676 cherrypy.request.headers.pop("Content-File-MD5", None)
677 elif (
678 "application/binary" in cherrypy.request.headers["Content-Type"]
679 or "application/gzip"
680 in cherrypy.request.headers["Content-Type"]
681 or "application/zip" in cherrypy.request.headers["Content-Type"]
682 or "text/plain" in cherrypy.request.headers["Content-Type"]
683 ):
684 indata = cherrypy.request.body # .read()
685 elif (
686 "multipart/form-data"
687 in cherrypy.request.headers["Content-Type"]
688 ):
689 if "descriptor_file" in kwargs:
690 filecontent = kwargs.pop("descriptor_file")
691 if not filecontent.file:
692 raise NbiException(
693 "empty file or content", HTTPStatus.BAD_REQUEST
694 )
695 indata = filecontent.file # .read()
696 if filecontent.content_type.value:
697 cherrypy.request.headers[
698 "Content-Type"
699 ] = filecontent.content_type.value
700 else:
701 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
702 # "Only 'Content-Type' of type 'application/json' or
703 # 'application/yaml' for input format are available")
704 error_text = "Invalid yaml format "
705 indata = yaml.safe_load(cherrypy.request.body)
706 cherrypy.request.headers.pop("Content-File-MD5", None)
707 else:
708 error_text = "Invalid yaml format "
709 indata = yaml.safe_load(cherrypy.request.body)
710 cherrypy.request.headers.pop("Content-File-MD5", None)
711 if not indata:
712 indata = {}
713
714 format_yaml = False
715 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
716 format_yaml = True
717
718 for k, v in kwargs.items():
719 if isinstance(v, str):
720 if v == "":
721 kwargs[k] = None
722 elif format_yaml:
723 try:
724 kwargs[k] = yaml.safe_load(v)
725 except Exception:
726 pass
727 elif (
728 k.endswith(".gt")
729 or k.endswith(".lt")
730 or k.endswith(".gte")
731 or k.endswith(".lte")
732 ):
733 try:
734 kwargs[k] = int(v)
735 except Exception:
736 try:
737 kwargs[k] = float(v)
738 except Exception:
739 pass
740 elif v.find(",") > 0:
741 kwargs[k] = v.split(",")
742 elif isinstance(v, (list, tuple)):
743 for index in range(0, len(v)):
744 if v[index] == "":
745 v[index] = None
746 elif format_yaml:
747 try:
748 v[index] = yaml.safe_load(v[index])
749 except Exception:
750 pass
751
752 return indata
753 except (ValueError, yaml.YAMLError) as exc:
754 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
755 except KeyError as exc:
756 raise NbiException(
757 "Query string error: " + str(exc), HTTPStatus.BAD_REQUEST
758 )
759 except Exception as exc:
760 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
761
762 @staticmethod
763 def _format_out(data, token_info=None, _format=None):
764 """
765 return string of dictionary data according to requested json, yaml, xml. By default json
766 :param data: response to be sent. Can be a dict, text or file
767 :param token_info: Contains among other username and project
768 :param _format: The format to be set as Content-Type if data is a file
769 :return: None
770 """
771 accept = cherrypy.request.headers.get("Accept")
772 if data is None:
773 if accept and "text/html" in accept:
774 return html.format(
775 data, cherrypy.request, cherrypy.response, token_info
776 )
777 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
778 return
779 elif hasattr(data, "read"): # file object
780 if _format:
781 cherrypy.response.headers["Content-Type"] = _format
782 elif "b" in data.mode: # binariy asssumig zip
783 cherrypy.response.headers["Content-Type"] = "application/zip"
784 else:
785 cherrypy.response.headers["Content-Type"] = "text/plain"
786 # TODO check that cherrypy close file. If not implement pending things to close per thread next
787 return data
788 if accept:
789 if "text/html" in accept:
790 return html.format(
791 data, cherrypy.request, cherrypy.response, token_info
792 )
793 elif "application/yaml" in accept or "*/*" in accept:
794 pass
795 elif "application/json" in accept or (
796 cherrypy.response.status and cherrypy.response.status >= 300
797 ):
798 cherrypy.response.headers[
799 "Content-Type"
800 ] = "application/json; charset=utf-8"
801 a = json.dumps(data, indent=4) + "\n"
802 return a.encode("utf8")
803 cherrypy.response.headers["Content-Type"] = "application/yaml"
804 return yaml.safe_dump(
805 data,
806 explicit_start=True,
807 indent=4,
808 default_flow_style=False,
809 tags=False,
810 encoding="utf-8",
811 allow_unicode=True,
812 ) # , canonical=True, default_style='"'
813
814 @cherrypy.expose
815 def index(self, *args, **kwargs):
816 token_info = None
817 try:
818 if cherrypy.request.method == "GET":
819 token_info = self.authenticator.authorize()
820 outdata = token_info # Home page
821 else:
822 raise cherrypy.HTTPError(
823 HTTPStatus.METHOD_NOT_ALLOWED.value,
824 "Method {} not allowed for tokens".format(cherrypy.request.method),
825 )
826
827 return self._format_out(outdata, token_info)
828
829 except (EngineException, AuthException) as e:
830 # cherrypy.log("index Exception {}".format(e))
831 cherrypy.response.status = e.http_code.value
832 return self._format_out("Welcome to OSM!", token_info)
833
834 @cherrypy.expose
835 def version(self, *args, **kwargs):
836 # TODO consider to remove and provide version using the static version file
837 try:
838 if cherrypy.request.method != "GET":
839 raise NbiException(
840 "Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED
841 )
842 elif args or kwargs:
843 raise NbiException(
844 "Invalid URL or query string for version",
845 HTTPStatus.METHOD_NOT_ALLOWED,
846 )
847 # TODO include version of other modules, pick up from some kafka admin message
848 osm_nbi_version = {"version": nbi_version, "date": nbi_version_date}
849 return self._format_out(osm_nbi_version)
850 except NbiException as e:
851 cherrypy.response.status = e.http_code.value
852 problem_details = {
853 "code": e.http_code.name,
854 "status": e.http_code.value,
855 "detail": str(e),
856 }
857 return self._format_out(problem_details, None)
858
859 def domain(self):
860 try:
861 domains = {
862 "user_domain_name": cherrypy.tree.apps["/osm"]
863 .config["authentication"]
864 .get("user_domain_name"),
865 "project_domain_name": cherrypy.tree.apps["/osm"]
866 .config["authentication"]
867 .get("project_domain_name"),
868 }
869 return self._format_out(domains)
870 except NbiException as e:
871 cherrypy.response.status = e.http_code.value
872 problem_details = {
873 "code": e.http_code.name,
874 "status": e.http_code.value,
875 "detail": str(e),
876 }
877 return self._format_out(problem_details, None)
878
879 @staticmethod
880 def _format_login(token_info):
881 """
882 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
883 log this information
884 :param token_info: Dictionary with token content
885 :return: None
886 """
887 cherrypy.request.login = token_info.get("username", "-")
888 if token_info.get("project_name"):
889 cherrypy.request.login += "/" + token_info["project_name"]
890 if token_info.get("id"):
891 cherrypy.request.login += ";session=" + token_info["id"][0:12]
892
893 # NS Fault Management
894 @cherrypy.expose
895 def nsfm(
896 self,
897 version=None,
898 topic=None,
899 uuid=None,
900 project_name=None,
901 ns_id=None,
902 *args,
903 **kwargs
904 ):
905 if topic == "alarms":
906 try:
907 method = cherrypy.request.method
908 role_permission = self._check_valid_url_method(
909 method, "nsfm", version, topic, None, None, *args
910 )
911 query_string_operations = self._extract_query_string_operations(
912 kwargs, method
913 )
914
915 self.authenticator.authorize(
916 role_permission, query_string_operations, None
917 )
918
919 # to handle get request
920 if cherrypy.request.method == "GET":
921 # if request is on basis of uuid
922 if uuid and uuid != "None":
923 try:
924 alarm = self.engine.db.get_one("alarms", {"uuid": uuid})
925 alarm_action = self.engine.db.get_one(
926 "alarms_action", {"uuid": uuid}
927 )
928 alarm.update(alarm_action)
929 vnf = self.engine.db.get_one(
930 "vnfrs", {"nsr-id-ref": alarm["tags"]["ns_id"]}
931 )
932 alarm["vnf-id"] = vnf["_id"]
933 return self._format_out(str(alarm))
934 except Exception:
935 return self._format_out("Please provide valid alarm uuid")
936 elif ns_id and ns_id != "None":
937 # if request is on basis of ns_id
938 try:
939 alarms = self.engine.db.get_list(
940 "alarms", {"tags.ns_id": ns_id}
941 )
942 for alarm in alarms:
943 alarm_action = self.engine.db.get_one(
944 "alarms_action", {"uuid": alarm["uuid"]}
945 )
946 alarm.update(alarm_action)
947 return self._format_out(str(alarms))
948 except Exception:
949 return self._format_out("Please provide valid ns id")
950 else:
951 # to return only alarm which are related to given project
952 project = self.engine.db.get_one(
953 "projects", {"name": project_name}
954 )
955 project_id = project.get("_id")
956 ns_list = self.engine.db.get_list(
957 "nsrs", {"_admin.projects_read": project_id}
958 )
959 ns_ids = []
960 for ns in ns_list:
961 ns_ids.append(ns.get("_id"))
962 alarms = self.engine.db.get_list("alarms")
963 alarm_list = [
964 alarm
965 for alarm in alarms
966 if alarm["tags"]["ns_id"] in ns_ids
967 ]
968 for alrm in alarm_list:
969 action = self.engine.db.get_one(
970 "alarms_action", {"uuid": alrm.get("uuid")}
971 )
972 alrm.update(action)
973 return self._format_out(str(alarm_list))
974 # to handle patch request for alarm update
975 elif cherrypy.request.method == "PATCH":
976 data = yaml.safe_load(cherrypy.request.body)
977 try:
978 # check if uuid is valid
979 self.engine.db.get_one("alarms", {"uuid": data.get("uuid")})
980 except Exception:
981 return self._format_out("Please provide valid alarm uuid.")
982 if data.get("is_enable") is not None:
983 if data.get("is_enable"):
984 alarm_status = "ok"
985 else:
986 alarm_status = "disabled"
987 self.engine.db.set_one(
988 "alarms",
989 {"uuid": data.get("uuid")},
990 {"alarm_status": alarm_status},
991 )
992 else:
993 self.engine.db.set_one(
994 "alarms",
995 {"uuid": data.get("uuid")},
996 {"threshold": data.get("threshold")},
997 )
998 return self._format_out("Alarm updated")
999 except Exception as e:
1000 if isinstance(
1001 e,
1002 (
1003 NbiException,
1004 EngineException,
1005 DbException,
1006 FsException,
1007 MsgException,
1008 AuthException,
1009 ValidationError,
1010 AuthconnException,
1011 ),
1012 ):
1013 http_code_value = cherrypy.response.status = e.http_code.value
1014 http_code_name = e.http_code.name
1015 cherrypy.log("Exception {}".format(e))
1016 else:
1017 http_code_value = (
1018 cherrypy.response.status
1019 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
1020 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
1021 http_code_name = HTTPStatus.BAD_REQUEST.name
1022 problem_details = {
1023 "code": http_code_name,
1024 "status": http_code_value,
1025 "detail": str(e),
1026 }
1027 return self._format_out(problem_details)
1028
1029 @cherrypy.expose
1030 def token(self, method, token_id=None, kwargs=None):
1031 token_info = None
1032 # self.engine.load_dbase(cherrypy.request.app.config)
1033 indata = self._format_in(kwargs)
1034 if not isinstance(indata, dict):
1035 raise NbiException(
1036 "Expected application/yaml or application/json Content-Type",
1037 HTTPStatus.BAD_REQUEST,
1038 )
1039
1040 if method == "GET":
1041 token_info = self.authenticator.authorize()
1042 # for logging
1043 self._format_login(token_info)
1044 if token_id:
1045 outdata = self.authenticator.get_token(token_info, token_id)
1046 else:
1047 outdata = self.authenticator.get_token_list(token_info)
1048 elif method == "POST":
1049 try:
1050 token_info = self.authenticator.authorize()
1051 except Exception:
1052 token_info = None
1053 if kwargs:
1054 indata.update(kwargs)
1055 # This is needed to log the user when authentication fails
1056 cherrypy.request.login = "{}".format(indata.get("username", "-"))
1057 outdata = token_info = self.authenticator.new_token(
1058 token_info, indata, cherrypy.request.remote
1059 )
1060 cherrypy.session["Authorization"] = outdata["_id"] # pylint: disable=E1101
1061 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
1062 # for logging
1063 self._format_login(token_info)
1064 # password expiry check
1065 if self.authenticator.check_password_expiry(outdata):
1066 outdata = {
1067 "id": outdata["id"],
1068 "message": "change_password",
1069 "user_id": outdata["user_id"],
1070 }
1071 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1072 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1073 elif method == "DELETE":
1074 if not token_id and "id" in kwargs:
1075 token_id = kwargs["id"]
1076 elif not token_id:
1077 token_info = self.authenticator.authorize()
1078 # for logging
1079 self._format_login(token_info)
1080 token_id = token_info["_id"]
1081 outdata = self.authenticator.del_token(token_id)
1082 token_info = None
1083 cherrypy.session["Authorization"] = "logout" # pylint: disable=E1101
1084 # cherrypy.response.cookie["Authorization"] = token_id
1085 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1086 else:
1087 raise NbiException(
1088 "Method {} not allowed for token".format(method),
1089 HTTPStatus.METHOD_NOT_ALLOWED,
1090 )
1091 return self._format_out(outdata, token_info)
1092
1093 @cherrypy.expose
1094 def test(self, *args, **kwargs):
1095 if not cherrypy.config.get("server.enable_test") or (
1096 isinstance(cherrypy.config["server.enable_test"], str)
1097 and cherrypy.config["server.enable_test"].lower() == "false"
1098 ):
1099 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value
1100 return "test URL is disabled"
1101 thread_info = None
1102 if args and args[0] == "help":
1103 return (
1104 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1105 "sleep/<time>\nmessage/topic\n</pre></html>"
1106 )
1107
1108 elif args and args[0] == "init":
1109 try:
1110 # self.engine.load_dbase(cherrypy.request.app.config)
1111 pid = self.authenticator.create_admin_project()
1112 self.authenticator.create_admin_user(pid)
1113 return "Done. User 'admin', password 'admin' created"
1114 except Exception:
1115 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1116 return self._format_out("Database already initialized")
1117 elif args and args[0] == "file":
1118 return cherrypy.lib.static.serve_file(
1119 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1],
1120 "text/plain",
1121 "attachment",
1122 )
1123 elif args and args[0] == "file2":
1124 f_path = (
1125 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1]
1126 )
1127 f = open(f_path, "r")
1128 cherrypy.response.headers["Content-type"] = "text/plain"
1129 return f
1130
1131 elif len(args) == 2 and args[0] == "db-clear":
1132 deleted_info = self.engine.db.del_list(args[1], kwargs)
1133 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
1134 elif len(args) and args[0] == "fs-clear":
1135 if len(args) >= 2:
1136 folders = (args[1],)
1137 else:
1138 folders = self.engine.fs.dir_ls(".")
1139 for folder in folders:
1140 self.engine.fs.file_delete(folder)
1141 return ",".join(folders) + " folders deleted\n"
1142 elif args and args[0] == "login":
1143 if not cherrypy.request.headers.get("Authorization"):
1144 cherrypy.response.headers[
1145 "WWW-Authenticate"
1146 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
1147 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1148 elif args and args[0] == "login2":
1149 if not cherrypy.request.headers.get("Authorization"):
1150 cherrypy.response.headers[
1151 "WWW-Authenticate"
1152 ] = 'Bearer realm="Access to OSM site"'
1153 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1154 elif args and args[0] == "sleep":
1155 sleep_time = 5
1156 try:
1157 sleep_time = int(args[1])
1158 except Exception:
1159 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1160 return self._format_out("Database already initialized")
1161 thread_info = cherrypy.thread_data
1162 print(thread_info)
1163 time.sleep(sleep_time)
1164 # thread_info
1165 elif len(args) >= 2 and args[0] == "message":
1166 main_topic = args[1]
1167 return_text = "<html><pre>{} ->\n".format(main_topic)
1168 try:
1169 if cherrypy.request.method == "POST":
1170 to_send = yaml.safe_load(cherrypy.request.body)
1171 for k, v in to_send.items():
1172 self.engine.msg.write(main_topic, k, v)
1173 return_text += " {}: {}\n".format(k, v)
1174 elif cherrypy.request.method == "GET":
1175 for k, v in kwargs.items():
1176 v_dict = yaml.safe_load(v)
1177 self.engine.msg.write(main_topic, k, v_dict)
1178 return_text += " {}: {}\n".format(k, v_dict)
1179 except Exception as e:
1180 return_text += "Error: " + str(e)
1181 return_text += "</pre></html>\n"
1182 return return_text
1183
1184 return_text = (
1185 "<html><pre>\nheaders:\n args: {}\n".format(args)
1186 + " kwargs: {}\n".format(kwargs)
1187 + " headers: {}\n".format(cherrypy.request.headers)
1188 + " path_info: {}\n".format(cherrypy.request.path_info)
1189 + " query_string: {}\n".format(cherrypy.request.query_string)
1190 + " session: {}\n".format(cherrypy.session) # pylint: disable=E1101
1191 + " cookie: {}\n".format(cherrypy.request.cookie)
1192 + " method: {}\n".format(cherrypy.request.method)
1193 + " session: {}\n".format(
1194 cherrypy.session.get("fieldname") # pylint: disable=E1101
1195 )
1196 + " body:\n"
1197 )
1198 return_text += " length: {}\n".format(cherrypy.request.body.length)
1199 if cherrypy.request.body.length:
1200 return_text += " content: {}\n".format(
1201 str(
1202 cherrypy.request.body.read(
1203 int(cherrypy.request.headers.get("Content-Length", 0))
1204 )
1205 )
1206 )
1207 if thread_info:
1208 return_text += "thread: {}\n".format(thread_info)
1209 return_text += "</pre></html>"
1210 return return_text
1211
1212 @staticmethod
1213 def _check_valid_url_method(method, *args):
1214 if len(args) < 3:
1215 raise NbiException(
1216 "URL must contain at least 'main_topic/version/topic'",
1217 HTTPStatus.METHOD_NOT_ALLOWED,
1218 )
1219
1220 reference = valid_url_methods
1221 for arg in args:
1222 if arg is None:
1223 break
1224 if not isinstance(reference, dict):
1225 raise NbiException(
1226 "URL contains unexpected extra items '{}'".format(arg),
1227 HTTPStatus.METHOD_NOT_ALLOWED,
1228 )
1229
1230 if arg in reference:
1231 reference = reference[arg]
1232 elif "<ID>" in reference:
1233 reference = reference["<ID>"]
1234 elif "*" in reference:
1235 # if there is content
1236 if reference["*"]:
1237 reference = reference["*"]
1238 break
1239 else:
1240 raise NbiException(
1241 "Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED
1242 )
1243 if "TODO" in reference and method in reference["TODO"]:
1244 raise NbiException(
1245 "Method {} not supported yet for this URL".format(method),
1246 HTTPStatus.NOT_IMPLEMENTED,
1247 )
1248 elif "METHODS" in reference and method not in reference["METHODS"]:
1249 raise NbiException(
1250 "Method {} not supported for this URL".format(method),
1251 HTTPStatus.METHOD_NOT_ALLOWED,
1252 )
1253 return reference["ROLE_PERMISSION"] + method.lower()
1254
1255 @staticmethod
1256 def _set_location_header(main_topic, version, topic, id):
1257 """
1258 Insert response header Location with the URL of created item base on URL params
1259 :param main_topic:
1260 :param version:
1261 :param topic:
1262 :param id:
1263 :return: None
1264 """
1265 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1266 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(
1267 main_topic, version, topic, id
1268 )
1269 return
1270
1271 @staticmethod
1272 def _extract_query_string_operations(kwargs, method):
1273 """
1274
1275 :param kwargs:
1276 :return:
1277 """
1278 query_string_operations = []
1279 if kwargs:
1280 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1281 if qs in kwargs and kwargs[qs].lower() != "false":
1282 query_string_operations.append(qs.lower() + ":" + method.lower())
1283 return query_string_operations
1284
1285 @staticmethod
1286 def _manage_admin_query(token_info, kwargs, method, _id):
1287 """
1288 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1289 Check that users has rights to use them and returs the admin_query
1290 :param token_info: token_info rights obtained by token
1291 :param kwargs: query string input.
1292 :param method: http method: GET, POSST, PUT, ...
1293 :param _id:
1294 :return: admin_query dictionary with keys:
1295 public: True, False or None
1296 force: True or False
1297 project_id: tuple with projects used for accessing an element
1298 set_project: tuple with projects that a created element will belong to
1299 method: show, list, delete, write
1300 """
1301 admin_query = {
1302 "force": False,
1303 "project_id": (token_info["project_id"],),
1304 "username": token_info["username"],
1305 "admin": token_info["admin"],
1306 "public": None,
1307 "allow_show_user_project_role": token_info["allow_show_user_project_role"],
1308 }
1309 if kwargs:
1310 # FORCE
1311 if "FORCE" in kwargs:
1312 if (
1313 kwargs["FORCE"].lower() != "false"
1314 ): # if None or True set force to True
1315 admin_query["force"] = True
1316 del kwargs["FORCE"]
1317 # PUBLIC
1318 if "PUBLIC" in kwargs:
1319 if (
1320 kwargs["PUBLIC"].lower() != "false"
1321 ): # if None or True set public to True
1322 admin_query["public"] = True
1323 else:
1324 admin_query["public"] = False
1325 del kwargs["PUBLIC"]
1326 # ADMIN
1327 if "ADMIN" in kwargs:
1328 behave_as = kwargs.pop("ADMIN")
1329 if behave_as.lower() != "false":
1330 if not token_info["admin"]:
1331 raise NbiException(
1332 "Only admin projects can use 'ADMIN' query string",
1333 HTTPStatus.UNAUTHORIZED,
1334 )
1335 if (
1336 not behave_as or behave_as.lower() == "true"
1337 ): # convert True, None to empty list
1338 admin_query["project_id"] = ()
1339 elif isinstance(behave_as, (list, tuple)):
1340 admin_query["project_id"] = behave_as
1341 else: # isinstance(behave_as, str)
1342 admin_query["project_id"] = (behave_as,)
1343 if "SET_PROJECT" in kwargs:
1344 set_project = kwargs.pop("SET_PROJECT")
1345 if not set_project:
1346 admin_query["set_project"] = list(admin_query["project_id"])
1347 else:
1348 if isinstance(set_project, str):
1349 set_project = (set_project,)
1350 if admin_query["project_id"]:
1351 for p in set_project:
1352 if p not in admin_query["project_id"]:
1353 raise NbiException(
1354 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1355 "'ADMIN='{p}'".format(p=p),
1356 HTTPStatus.UNAUTHORIZED,
1357 )
1358 admin_query["set_project"] = set_project
1359
1360 # PROJECT_READ
1361 # if "PROJECT_READ" in kwargs:
1362 # admin_query["project"] = kwargs.pop("project")
1363 # if admin_query["project"] == token_info["project_id"]:
1364 if method == "GET":
1365 if _id:
1366 admin_query["method"] = "show"
1367 else:
1368 admin_query["method"] = "list"
1369 elif method == "DELETE":
1370 admin_query["method"] = "delete"
1371 else:
1372 admin_query["method"] = "write"
1373 return admin_query
1374
1375 @cherrypy.expose
1376 def default(
1377 self,
1378 main_topic=None,
1379 version=None,
1380 topic=None,
1381 _id=None,
1382 item=None,
1383 *args,
1384 **kwargs
1385 ):
1386 token_info = None
1387 outdata = None
1388 _format = None
1389 method = "DONE"
1390 engine_topic = None
1391 rollback = []
1392 engine_session = None
1393 try:
1394 if not main_topic or not version or not topic:
1395 raise NbiException(
1396 "URL must contain at least 'main_topic/version/topic'",
1397 HTTPStatus.METHOD_NOT_ALLOWED,
1398 )
1399 if main_topic not in (
1400 "admin",
1401 "vnfpkgm",
1402 "nsd",
1403 "nslcm",
1404 "pdu",
1405 "nst",
1406 "nsilcm",
1407 "nspm",
1408 "vnflcm",
1409 ):
1410 raise NbiException(
1411 "URL main_topic '{}' not supported".format(main_topic),
1412 HTTPStatus.METHOD_NOT_ALLOWED,
1413 )
1414 if version != "v1":
1415 raise NbiException(
1416 "URL version '{}' not supported".format(version),
1417 HTTPStatus.METHOD_NOT_ALLOWED,
1418 )
1419
1420 if (
1421 kwargs
1422 and "METHOD" in kwargs
1423 and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1424 ):
1425 method = kwargs.pop("METHOD")
1426 else:
1427 method = cherrypy.request.method
1428
1429 role_permission = self._check_valid_url_method(
1430 method, main_topic, version, topic, _id, item, *args
1431 )
1432 query_string_operations = self._extract_query_string_operations(
1433 kwargs, method
1434 )
1435 if main_topic == "admin" and topic == "tokens":
1436 return self.token(method, _id, kwargs)
1437 token_info = self.authenticator.authorize(
1438 role_permission, query_string_operations, _id
1439 )
1440 if main_topic == "admin" and topic == "domains":
1441 return self.domain()
1442 engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
1443 indata = self._format_in(kwargs)
1444 engine_topic = topic
1445
1446 if item and topic != "pm_jobs":
1447 engine_topic = item
1448
1449 if main_topic == "nsd":
1450 engine_topic = "nsds"
1451 elif main_topic == "vnfpkgm":
1452 engine_topic = "vnfds"
1453 if topic == "vnfpkg_op_occs":
1454 engine_topic = "vnfpkgops"
1455 if topic == "vnf_packages" and item == "action":
1456 engine_topic = "vnfpkgops"
1457 elif main_topic == "nslcm":
1458 engine_topic = "nsrs"
1459 if topic == "ns_lcm_op_occs":
1460 engine_topic = "nslcmops"
1461 if topic == "vnfrs" or topic == "vnf_instances":
1462 engine_topic = "vnfrs"
1463 elif main_topic == "vnflcm":
1464 if topic == "vnf_lcm_op_occs":
1465 engine_topic = "vnflcmops"
1466 elif main_topic == "nst":
1467 engine_topic = "nsts"
1468 elif main_topic == "nsilcm":
1469 engine_topic = "nsis"
1470 if topic == "nsi_lcm_op_occs":
1471 engine_topic = "nsilcmops"
1472 elif main_topic == "pdu":
1473 engine_topic = "pdus"
1474 if (
1475 engine_topic == "vims"
1476 ): # TODO this is for backward compatibility, it will be removed in the future
1477 engine_topic = "vim_accounts"
1478
1479 if topic == "subscriptions":
1480 engine_topic = main_topic + "_" + topic
1481
1482 if method == "GET":
1483 if item in (
1484 "nsd_content",
1485 "package_content",
1486 "artifacts",
1487 "vnfd",
1488 "nsd",
1489 "nst",
1490 "nst_content",
1491 ):
1492 if item in ("vnfd", "nsd", "nst"):
1493 path = "$DESCRIPTOR"
1494 elif args:
1495 path = args
1496 elif item == "artifacts":
1497 path = ()
1498 else:
1499 path = None
1500 file, _format = self.engine.get_file(
1501 engine_session,
1502 engine_topic,
1503 _id,
1504 path,
1505 cherrypy.request.headers.get("Accept"),
1506 )
1507 outdata = file
1508 elif not _id:
1509 outdata = self.engine.get_item_list(
1510 engine_session, engine_topic, kwargs, api_req=True
1511 )
1512 else:
1513 if item == "reports":
1514 # TODO check that project_id (_id in this context) has permissions
1515 _id = args[0]
1516 filter_q = None
1517 if "vcaStatusRefresh" in kwargs:
1518 filter_q = {"vcaStatusRefresh": kwargs["vcaStatusRefresh"]}
1519 outdata = self.engine.get_item(
1520 engine_session, engine_topic, _id, filter_q, True
1521 )
1522
1523 elif method == "POST":
1524 cherrypy.response.status = HTTPStatus.CREATED.value
1525 if topic in (
1526 "ns_descriptors_content",
1527 "vnf_packages_content",
1528 "netslice_templates_content",
1529 ):
1530 _id = cherrypy.request.headers.get("Transaction-Id")
1531 if not _id:
1532 _id, _ = self.engine.new_item(
1533 rollback,
1534 engine_session,
1535 engine_topic,
1536 {},
1537 None,
1538 cherrypy.request.headers,
1539 )
1540 completed = self.engine.upload_content(
1541 engine_session,
1542 engine_topic,
1543 _id,
1544 indata,
1545 kwargs,
1546 cherrypy.request.headers,
1547 )
1548 if completed:
1549 self._set_location_header(main_topic, version, topic, _id)
1550 else:
1551 cherrypy.response.headers["Transaction-Id"] = _id
1552 outdata = {"id": _id}
1553 elif topic == "ns_instances_content":
1554 # creates NSR
1555 _id, _ = self.engine.new_item(
1556 rollback, engine_session, engine_topic, indata, kwargs
1557 )
1558 # creates nslcmop
1559 indata["lcmOperationType"] = "instantiate"
1560 indata["nsInstanceId"] = _id
1561 nslcmop_id, _ = self.engine.new_item(
1562 rollback, engine_session, "nslcmops", indata, None
1563 )
1564 self._set_location_header(main_topic, version, topic, _id)
1565 outdata = {"id": _id, "nslcmop_id": nslcmop_id}
1566 elif topic == "ns_instances" and item:
1567 indata["lcmOperationType"] = item
1568 indata["nsInstanceId"] = _id
1569 _id, _ = self.engine.new_item(
1570 rollback, engine_session, "nslcmops", indata, kwargs
1571 )
1572 self._set_location_header(
1573 main_topic, version, "ns_lcm_op_occs", _id
1574 )
1575 outdata = {"id": _id}
1576 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1577 elif topic == "netslice_instances_content":
1578 # creates NetSlice_Instance_record (NSIR)
1579 _id, _ = self.engine.new_item(
1580 rollback, engine_session, engine_topic, indata, kwargs
1581 )
1582 self._set_location_header(main_topic, version, topic, _id)
1583 indata["lcmOperationType"] = "instantiate"
1584 indata["netsliceInstanceId"] = _id
1585 nsilcmop_id, _ = self.engine.new_item(
1586 rollback, engine_session, "nsilcmops", indata, kwargs
1587 )
1588 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
1589 elif topic == "netslice_instances" and item:
1590 indata["lcmOperationType"] = item
1591 indata["netsliceInstanceId"] = _id
1592 _id, _ = self.engine.new_item(
1593 rollback, engine_session, "nsilcmops", indata, kwargs
1594 )
1595 self._set_location_header(
1596 main_topic, version, "nsi_lcm_op_occs", _id
1597 )
1598 outdata = {"id": _id}
1599 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1600 elif topic == "vnf_packages" and item == "action":
1601 indata["lcmOperationType"] = item
1602 indata["vnfPkgId"] = _id
1603 _id, _ = self.engine.new_item(
1604 rollback, engine_session, "vnfpkgops", indata, kwargs
1605 )
1606 self._set_location_header(
1607 main_topic, version, "vnfpkg_op_occs", _id
1608 )
1609 outdata = {"id": _id}
1610 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1611 elif topic == "subscriptions":
1612 _id, _ = self.engine.new_item(
1613 rollback, engine_session, engine_topic, indata, kwargs
1614 )
1615 self._set_location_header(main_topic, version, topic, _id)
1616 link = {}
1617 link["self"] = cherrypy.response.headers["Location"]
1618 outdata = {
1619 "id": _id,
1620 "filter": indata["filter"],
1621 "callbackUri": indata["CallbackUri"],
1622 "_links": link,
1623 }
1624 cherrypy.response.status = HTTPStatus.CREATED.value
1625 elif topic == "vnf_instances" and item:
1626 indata["lcmOperationType"] = item
1627 indata["vnfInstanceId"] = _id
1628 _id, _ = self.engine.new_item(
1629 rollback, engine_session, "vnflcmops", indata, kwargs
1630 )
1631 self._set_location_header(
1632 main_topic, version, "vnf_lcm_op_occs", _id
1633 )
1634 outdata = {"id": _id}
1635 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1636 else:
1637 _id, op_id = self.engine.new_item(
1638 rollback,
1639 engine_session,
1640 engine_topic,
1641 indata,
1642 kwargs,
1643 cherrypy.request.headers,
1644 )
1645 self._set_location_header(main_topic, version, topic, _id)
1646 outdata = {"id": _id}
1647 if op_id:
1648 outdata["op_id"] = op_id
1649 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1650 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1651
1652 elif method == "DELETE":
1653 if not _id:
1654 outdata = self.engine.del_item_list(
1655 engine_session, engine_topic, kwargs
1656 )
1657 cherrypy.response.status = HTTPStatus.OK.value
1658 else: # len(args) > 1
1659 # for NS NSI generate an operation
1660 op_id = None
1661 if topic == "ns_instances_content" and not engine_session["force"]:
1662 nslcmop_desc = {
1663 "lcmOperationType": "terminate",
1664 "nsInstanceId": _id,
1665 "autoremove": True,
1666 }
1667 op_id, _ = self.engine.new_item(
1668 rollback, engine_session, "nslcmops", nslcmop_desc, kwargs
1669 )
1670 if op_id:
1671 outdata = {"_id": op_id}
1672 elif (
1673 topic == "netslice_instances_content"
1674 and not engine_session["force"]
1675 ):
1676 nsilcmop_desc = {
1677 "lcmOperationType": "terminate",
1678 "netsliceInstanceId": _id,
1679 "autoremove": True,
1680 }
1681 op_id, _ = self.engine.new_item(
1682 rollback, engine_session, "nsilcmops", nsilcmop_desc, None
1683 )
1684 if op_id:
1685 outdata = {"_id": op_id}
1686 # if there is not any deletion in process, delete
1687 if not op_id:
1688 op_id = self.engine.del_item(engine_session, engine_topic, _id)
1689 if op_id:
1690 outdata = {"op_id": op_id}
1691 cherrypy.response.status = (
1692 HTTPStatus.ACCEPTED.value
1693 if op_id
1694 else HTTPStatus.NO_CONTENT.value
1695 )
1696
1697 elif method in ("PUT", "PATCH"):
1698 op_id = None
1699 if not indata and not kwargs and not engine_session.get("set_project"):
1700 raise NbiException(
1701 "Nothing to update. Provide payload and/or query string",
1702 HTTPStatus.BAD_REQUEST,
1703 )
1704 if (
1705 item in ("nsd_content", "package_content", "nst_content")
1706 and method == "PUT"
1707 ):
1708 completed = self.engine.upload_content(
1709 engine_session,
1710 engine_topic,
1711 _id,
1712 indata,
1713 kwargs,
1714 cherrypy.request.headers,
1715 )
1716 if not completed:
1717 cherrypy.response.headers["Transaction-Id"] = id
1718 else:
1719 op_id = self.engine.edit_item(
1720 engine_session, engine_topic, _id, indata, kwargs
1721 )
1722
1723 if op_id:
1724 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1725 outdata = {"op_id": op_id}
1726 else:
1727 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
1728 outdata = None
1729 else:
1730 raise NbiException(
1731 "Method {} not allowed".format(method),
1732 HTTPStatus.METHOD_NOT_ALLOWED,
1733 )
1734
1735 # if Role information changes, it is needed to reload the information of roles
1736 if topic == "roles" and method != "GET":
1737 self.authenticator.load_operation_to_allowed_roles()
1738
1739 if (
1740 topic == "projects"
1741 and method == "DELETE"
1742 or topic in ["users", "roles"]
1743 and method in ["PUT", "PATCH", "DELETE"]
1744 ):
1745 self.authenticator.remove_token_from_cache()
1746
1747 return self._format_out(outdata, token_info, _format)
1748 except Exception as e:
1749 if isinstance(
1750 e,
1751 (
1752 NbiException,
1753 EngineException,
1754 DbException,
1755 FsException,
1756 MsgException,
1757 AuthException,
1758 ValidationError,
1759 AuthconnException,
1760 ),
1761 ):
1762 http_code_value = cherrypy.response.status = e.http_code.value
1763 http_code_name = e.http_code.name
1764 cherrypy.log("Exception {}".format(e))
1765 else:
1766 http_code_value = (
1767 cherrypy.response.status
1768 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
1769 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
1770 http_code_name = HTTPStatus.BAD_REQUEST.name
1771 if hasattr(outdata, "close"): # is an open file
1772 outdata.close()
1773 error_text = str(e)
1774 rollback.reverse()
1775 for rollback_item in rollback:
1776 try:
1777 if rollback_item.get("operation") == "set":
1778 self.engine.db.set_one(
1779 rollback_item["topic"],
1780 {"_id": rollback_item["_id"]},
1781 rollback_item["content"],
1782 fail_on_empty=False,
1783 )
1784 elif rollback_item.get("operation") == "del_list":
1785 self.engine.db.del_list(
1786 rollback_item["topic"],
1787 rollback_item["filter"],
1788 )
1789 else:
1790 self.engine.db.del_one(
1791 rollback_item["topic"],
1792 {"_id": rollback_item["_id"]},
1793 fail_on_empty=False,
1794 )
1795 except Exception as e2:
1796 rollback_error_text = "Rollback Exception {}: {}".format(
1797 rollback_item, e2
1798 )
1799 cherrypy.log(rollback_error_text)
1800 error_text += ". " + rollback_error_text
1801 # if isinstance(e, MsgException):
1802 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1803 # engine_topic[:-1], method, error_text)
1804 problem_details = {
1805 "code": http_code_name,
1806 "status": http_code_value,
1807 "detail": error_text,
1808 }
1809 return self._format_out(problem_details, token_info)
1810 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1811 finally:
1812 if token_info:
1813 self._format_login(token_info)
1814 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
1815 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1816 if outdata.get(logging_id):
1817 cherrypy.request.login += ";{}={}".format(
1818 logging_id, outdata[logging_id][:36]
1819 )
1820
1821
1822 def _start_service():
1823 """
1824 Callback function called when cherrypy.engine starts
1825 Override configuration with env variables
1826 Set database, storage, message configuration
1827 Init database with admin/admin user password
1828 """
1829 global nbi_server
1830 global subscription_thread
1831 cherrypy.log.error("Starting osm_nbi")
1832 # update general cherrypy configuration
1833 update_dict = {}
1834
1835 engine_config = cherrypy.tree.apps["/osm"].config
1836 for k, v in environ.items():
1837 if not k.startswith("OSMNBI_"):
1838 continue
1839 k1, _, k2 = k[7:].lower().partition("_")
1840 if not k2:
1841 continue
1842 try:
1843 # update static configuration
1844 if k == "OSMNBI_STATIC_DIR":
1845 engine_config["/static"]["tools.staticdir.dir"] = v
1846 engine_config["/static"]["tools.staticdir.on"] = True
1847 elif k == "OSMNBI_SOCKET_PORT" or k == "OSMNBI_SERVER_PORT":
1848 update_dict["server.socket_port"] = int(v)
1849 elif k == "OSMNBI_SOCKET_HOST" or k == "OSMNBI_SERVER_HOST":
1850 update_dict["server.socket_host"] = v
1851 elif k1 in ("server", "test", "auth", "log"):
1852 update_dict[k1 + "." + k2] = v
1853 elif k1 in ("message", "database", "storage", "authentication"):
1854 # k2 = k2.replace('_', '.')
1855 if k2 in ("port", "db_port"):
1856 engine_config[k1][k2] = int(v)
1857 else:
1858 engine_config[k1][k2] = v
1859
1860 except ValueError as e:
1861 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1862 except Exception as e:
1863 cherrypy.log(
1864 "WARNING: skipping environ '{}' on exception '{}'".format(k, e)
1865 )
1866
1867 if update_dict:
1868 cherrypy.config.update(update_dict)
1869 engine_config["global"].update(update_dict)
1870
1871 # logging cherrypy
1872 log_format_simple = (
1873 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1874 )
1875 log_formatter_simple = logging.Formatter(
1876 log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S"
1877 )
1878 logger_server = logging.getLogger("cherrypy.error")
1879 logger_access = logging.getLogger("cherrypy.access")
1880 logger_cherry = logging.getLogger("cherrypy")
1881 logger_nbi = logging.getLogger("nbi")
1882
1883 if "log.file" in engine_config["global"]:
1884 file_handler = logging.handlers.RotatingFileHandler(
1885 engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0
1886 )
1887 file_handler.setFormatter(log_formatter_simple)
1888 logger_cherry.addHandler(file_handler)
1889 logger_nbi.addHandler(file_handler)
1890 # log always to standard output
1891 for format_, logger in {
1892 "nbi.server %(filename)s:%(lineno)s": logger_server,
1893 "nbi.access %(filename)s:%(lineno)s": logger_access,
1894 "%(name)s %(filename)s:%(lineno)s": logger_nbi,
1895 }.items():
1896 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
1897 log_formatter_cherry = logging.Formatter(
1898 log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S"
1899 )
1900 str_handler = logging.StreamHandler()
1901 str_handler.setFormatter(log_formatter_cherry)
1902 logger.addHandler(str_handler)
1903
1904 if engine_config["global"].get("log.level"):
1905 logger_cherry.setLevel(engine_config["global"]["log.level"])
1906 logger_nbi.setLevel(engine_config["global"]["log.level"])
1907
1908 # logging other modules
1909 for k1, logname in {
1910 "message": "nbi.msg",
1911 "database": "nbi.db",
1912 "storage": "nbi.fs",
1913 }.items():
1914 engine_config[k1]["logger_name"] = logname
1915 logger_module = logging.getLogger(logname)
1916 if "logfile" in engine_config[k1]:
1917 file_handler = logging.handlers.RotatingFileHandler(
1918 engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0
1919 )
1920 file_handler.setFormatter(log_formatter_simple)
1921 logger_module.addHandler(file_handler)
1922 if "loglevel" in engine_config[k1]:
1923 logger_module.setLevel(engine_config[k1]["loglevel"])
1924 # TODO add more entries, e.g.: storage
1925 cherrypy.tree.apps["/osm"].root.engine.start(engine_config)
1926 cherrypy.tree.apps["/osm"].root.authenticator.start(engine_config)
1927 cherrypy.tree.apps["/osm"].root.engine.init_db(target_version=database_version)
1928 cherrypy.tree.apps["/osm"].root.authenticator.init_db(
1929 target_version=auth_database_version
1930 )
1931
1932 # start subscriptions thread:
1933 subscription_thread = SubscriptionThread(
1934 config=engine_config, engine=nbi_server.engine
1935 )
1936 subscription_thread.start()
1937 # Do not capture except SubscriptionException
1938
1939 backend = engine_config["authentication"]["backend"]
1940 cherrypy.log.error(
1941 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1942 nbi_version, nbi_version_date, backend
1943 )
1944 )
1945
1946
1947 def _stop_service():
1948 """
1949 Callback function called when cherrypy.engine stops
1950 TODO: Ending database connections.
1951 """
1952 global subscription_thread
1953 if subscription_thread:
1954 subscription_thread.terminate()
1955 subscription_thread = None
1956 cherrypy.tree.apps["/osm"].root.engine.stop()
1957 cherrypy.log.error("Stopping osm_nbi")
1958
1959
1960 def nbi(config_file):
1961 global nbi_server
1962 # conf = {
1963 # '/': {
1964 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1965 # 'tools.sessions.on': True,
1966 # 'tools.response_headers.on': True,
1967 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1968 # }
1969 # }
1970 # cherrypy.Server.ssl_module = 'builtin'
1971 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1972 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1973 # cherrypy.Server.thread_pool = 10
1974 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1975
1976 # cherrypy.config.update({'tools.auth_basic.on': True,
1977 # 'tools.auth_basic.realm': 'localhost',
1978 # 'tools.auth_basic.checkpassword': validate_password})
1979 nbi_server = Server()
1980 cherrypy.engine.subscribe("start", _start_service)
1981 cherrypy.engine.subscribe("stop", _stop_service)
1982 cherrypy.quickstart(nbi_server, "/osm", config_file)
1983
1984
1985 def usage():
1986 print(
1987 """Usage: {} [options]
1988 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1989 -h|--help: shows this help
1990 """.format(
1991 sys.argv[0]
1992 )
1993 )
1994 # --log-socket-host HOST: send logs to this host")
1995 # --log-socket-port PORT: send logs using this port (default: 9022)")
1996
1997
1998 if __name__ == "__main__":
1999 try:
2000 # load parameters and configuration
2001 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
2002 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
2003 config_file = None
2004 for o, a in opts:
2005 if o in ("-h", "--help"):
2006 usage()
2007 sys.exit()
2008 elif o in ("-c", "--config"):
2009 config_file = a
2010 # elif o == "--log-socket-port":
2011 # log_socket_port = a
2012 # elif o == "--log-socket-host":
2013 # log_socket_host = a
2014 # elif o == "--log-file":
2015 # log_file = a
2016 else:
2017 assert False, "Unhandled option"
2018 if config_file:
2019 if not path.isfile(config_file):
2020 print(
2021 "configuration file '{}' that not exist".format(config_file),
2022 file=sys.stderr,
2023 )
2024 exit(1)
2025 else:
2026 for config_file in (
2027 __file__[: __file__.rfind(".")] + ".cfg",
2028 "./nbi.cfg",
2029 "/etc/osm/nbi.cfg",
2030 ):
2031 if path.isfile(config_file):
2032 break
2033 else:
2034 print(
2035 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
2036 file=sys.stderr,
2037 )
2038 exit(1)
2039 nbi(config_file)
2040 except getopt.GetoptError as e:
2041 print(str(e), file=sys.stderr)
2042 # usage()
2043 exit(1)