Code Coverage

Cobertura Coverage Report > osm_nbi >

nbi.py

Trend

Classes0%
 
Lines0%
 
Conditionals100%
 

File Coverage summary

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

Coverage Breakdown by Class

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