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