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