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