Code Coverage

Cobertura Coverage Report > osm_nbi >

nbi.py

Trend

File Coverage summary

NameClassesLinesConditionals
nbi.py
0%
0/1
0%
0/792
100%
0/0

Coverage Breakdown by Class

NameLinesConditionals
nbi.py
0%
0/792
N/A

Source

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