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