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