Code Coverage

Cobertura Coverage Report > osm_nbi >

nbi.py

Trend

Classes0%
 
Lines0%
 
Conditionals100%
 

File Coverage summary

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

Coverage Breakdown by Class

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