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