blob: 1cd13b799af624bd63085018526885fe11e3b019 [file] [log] [blame]
tiernoc94c3df2018-02-09 15:38:54 +01001#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3
tiernod125caf2018-11-22 16:05:54 +00004# 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
tiernoc94c3df2018-02-09 15:38:54 +010017import cherrypy
18import time
19import json
20import yaml
tierno9c630112019-08-29 14:21:41 +000021import osm_nbi.html_out as html
tiernoc94c3df2018-02-09 15:38:54 +010022import logging
tiernof5298be2018-05-16 14:43:57 +020023import logging.handlers
24import getopt
25import sys
Eduardo Sousa2f988212018-07-26 01:04:11 +010026
tierno9c630112019-08-29 14:21:41 +000027from osm_nbi.authconn import AuthException, AuthconnException
28from osm_nbi.auth import Authenticator
29from osm_nbi.engine import Engine, EngineException
30from osm_nbi.subscriptions import SubscriptionThread
31from osm_nbi.validation import ValidationError
tiernoa8d63632018-05-10 13:12:32 +020032from osm_common.dbbase import DbException
33from osm_common.fsbase import FsException
34from osm_common.msgbase import MsgException
Patricia Reinoso62fa6732023-02-22 17:57:53 +000035from osm_common.wftemporal import WFTemporal
tiernoc94c3df2018-02-09 15:38:54 +010036from http import HTTPStatus
tiernoc94c3df2018-02-09 15:38:54 +010037from codecs import getreader
tiernof5298be2018-05-16 14:43:57 +020038from os import environ, path
tiernob2e48bd2020-02-04 15:47:18 +000039from osm_nbi import version as nbi_version, version_date as nbi_version_date
tiernoc94c3df2018-02-09 15:38:54 +010040
41__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
tiernodfe09572018-04-24 10:41:10 +020042
garciadeblas4568a372021-03-24 09:19:48 +010043__version__ = "0.1.3" # file version, not NBI version
tierno9c630112019-08-29 14:21:41 +000044version_date = "Aug 2019"
45
garciadeblas4568a372021-03-24 09:19:48 +010046database_version = "1.2"
47auth_database_version = "1.0"
48nbi_server = None # instance of Server class
tierno932499c2019-01-28 17:28:10 +000049subscription_thread = None # instance of SubscriptionThread class
tiernoc94c3df2018-02-09 15:38:54 +010050
51"""
tiernof27c79b2018-03-12 17:08:42 +010052North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
tiernoc94c3df2018-02-09 15:38:54 +010053URL: /osm GET POST PUT DELETE PATCH
garciadeblas9750c5a2018-10-15 16:20:35 +020054 /nsd/v1
tierno2236d202018-05-16 19:05:16 +020055 /ns_descriptors_content O O
56 /<nsdInfoId> O O O O
tiernoc94c3df2018-02-09 15:38:54 +010057 /ns_descriptors O5 O5
58 /<nsdInfoId> O5 O5 5
59 /nsd_content O5 O5
tiernof27c79b2018-03-12 17:08:42 +010060 /nsd O
61 /artifacts[/<artifactPath>] O
tiernoc94c3df2018-02-09 15:38:54 +010062 /pnf_descriptors 5 5
63 /<pnfdInfoId> 5 5 5
64 /pnfd_content 5 5
tiernof27c79b2018-03-12 17:08:42 +010065 /subscriptions 5 5
66 /<subscriptionId> 5 X
tiernoc94c3df2018-02-09 15:38:54 +010067
68 /vnfpkgm/v1
tierno55945e72018-04-06 16:40:27 +020069 /vnf_packages_content O O
tierno2236d202018-05-16 19:05:16 +020070 /<vnfPkgId> O O
tiernoc94c3df2018-02-09 15:38:54 +010071 /vnf_packages O5 O5
72 /<vnfPkgId> O5 O5 5
tiernoc94c3df2018-02-09 15:38:54 +010073 /package_content O5 O5
74 /upload_from_uri X
tiernof27c79b2018-03-12 17:08:42 +010075 /vnfd O5
76 /artifacts[/<artifactPath>] O5
77 /subscriptions X X
78 /<subscriptionId> X X
tiernoc94c3df2018-02-09 15:38:54 +010079
80 /nslcm/v1
tiernof27c79b2018-03-12 17:08:42 +010081 /ns_instances_content O O
tierno2236d202018-05-16 19:05:16 +020082 /<nsInstanceId> O O
tiernof27c79b2018-03-12 17:08:42 +010083 /ns_instances 5 5
tierno95692442018-05-24 18:05:28 +020084 /<nsInstanceId> O5 O5
tierno65acb4d2018-04-06 16:42:40 +020085 instantiate O5
86 terminate O5
87 action O
88 scale O5
elumalai8e3806c2022-04-28 17:26:24 +053089 migrate O
aticig544a2ae2022-04-05 09:00:17 +030090 update 05
garciadeblas0964edf2022-02-11 00:43:44 +010091 heal O5
tiernoc94c3df2018-02-09 15:38:54 +010092 /ns_lcm_op_occs 5 5
93 /<nsLcmOpOccId> 5 5 5
94 TO BE COMPLETED 5 5
tiernof759d822018-06-11 18:54:54 +020095 /vnf_instances (also vnfrs for compatibility) O
96 /<vnfInstanceId> O
tiernof27c79b2018-03-12 17:08:42 +010097 /subscriptions 5 5
98 /<subscriptionId> 5 X
garciadeblas9750c5a2018-10-15 16:20:35 +020099
tiernocb83c942018-09-24 17:28:13 +0200100 /pdu/v1
tierno032916c2019-03-22 13:27:12 +0000101 /pdu_descriptors O O
tiernocb83c942018-09-24 17:28:13 +0200102 /<id> O O O O
garciadeblas9750c5a2018-10-15 16:20:35 +0200103
tiernof27c79b2018-03-12 17:08:42 +0100104 /admin/v1
105 /tokens O O
tierno2236d202018-05-16 19:05:16 +0200106 /<id> O O
tiernof27c79b2018-03-12 17:08:42 +0100107 /users O O
tiernocd54a4a2018-09-12 16:40:35 +0200108 /<id> O O O O
tiernof27c79b2018-03-12 17:08:42 +0100109 /projects O O
tierno2236d202018-05-16 19:05:16 +0200110 /<id> O O
tierno55ba2e62018-12-11 17:22:22 +0000111 /vim_accounts (also vims for compatibility) O O
112 /<id> O O O
113 /wim_accounts O O
tierno2236d202018-05-16 19:05:16 +0200114 /<id> O O O
tierno0f98af52018-03-19 10:28:22 +0100115 /sdns O O
tierno2236d202018-05-16 19:05:16 +0200116 /<id> O O O
delacruzramofe598fe2019-10-23 18:25:11 +0200117 /k8sclusters O O
118 /<id> O O O
119 /k8srepos O O
120 /<id> O O
Felipe Vicensb66b0412020-05-06 10:11:00 +0200121 /osmrepos O O
122 /<id> O O
tiernoc94c3df2018-02-09 15:38:54 +0100123
garciadeblas9750c5a2018-10-15 16:20:35 +0200124 /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
tierno2236d202018-05-16 19:05:16 +0200148query string:
149 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
tierno36ec8602018-11-02 17:27:11 +0100150 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
tierno2236d202018-05-16 19:05:16 +0200154 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,...
tiernoc94c3df2018-02-09 15:38:54 +0100159 (none) … same as “exclude_default”
160 all_fields … all attributes.
tierno2236d202018-05-16 19:05:16 +0200161 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>
tierno65ca36d2019-02-12 19:27:52 +0100171 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
beierlmbc5a5242022-05-17 21:25:29 -0400176
tiernoc94c3df2018-02-09 15:38:54 +0100177Header 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.
tierno2236d202018-05-16 19:05:16 +0200182 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.
tiernoc94c3df2018-02-09 15:38:54 +0100184 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
185Header 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.
tierno2236d202018-05-16 19:05:16 +0200188 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.
tiernoc94c3df2018-02-09 15:38:54 +0100190 This header field shall be present if the response status code is 201 or 3xx.
tierno2236d202018-05-16 19:05:16 +0200191 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.
tiernoc94c3df2018-02-09 15:38:54 +0100200 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
tiernoc94c3df2018-02-09 15:38:54 +0100201"""
202
tierno701018c2019-06-25 11:13:14 +0000203valid_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
211valid_url_methods = {
212 # contains allowed URL and methods, and the role_permission name
213 "admin": {
214 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100215 "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 },
tierno701018c2019-06-25 11:13:14 +0000312 }
313 },
314 "pdu": {
315 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100316 "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 },
tierno701018c2019-06-25 11:13:14 +0000324 }
325 },
326 "nsd": {
327 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100328 "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 },
tierno701018c2019-06-25 11:13:14 +0000368 }
369 },
370 "vnfpkgm": {
371 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100372 "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 },
tierno701018c2019-06-25 11:13:14 +0000419 }
420 },
421 "nslcm": {
422 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100423 "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:",
garciadeblas0964edf2022-02-11 00:43:44 +0100437 "heal": {
438 "METHODS": ("POST",),
439 "ROLE_PERMISSION": "ns_instances:id:heal:",
440 },
garciadeblas4568a372021-03-24 09:19:48 +0100441 "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 },
elumalai8e3806c2022-04-28 17:26:24 +0530453 "migrate": {
454 "METHODS": ("POST",),
455 "ROLE_PERMISSION": "ns_instances:id:migrate:",
456 },
garciadeblas4568a372021-03-24 09:19:48 +0100457 "action": {
458 "METHODS": ("POST",),
459 "ROLE_PERMISSION": "ns_instances:id:action:",
460 },
aticig544a2ae2022-04-05 09:00:17 +0300461 "update": {
462 "METHODS": ("POST",),
463 "ROLE_PERMISSION": "ns_instances:id:update:",
464 },
govindarajul519da482022-04-29 19:05:22 +0530465 "verticalscale": {
466 "METHODS": ("POST",),
garciadeblasf2af4a12023-01-24 16:56:54 +0100467 "ROLE_PERMISSION": "ns_instances:id:verticalscale:",
468 },
garciadeblas4568a372021-03-24 09:19:48 +0100469 },
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 },
tierno701018c2019-06-25 11:13:14 +0000497 }
498 },
almagiae47b9132022-05-17 14:12:22 +0200499 "vnflcm": {
500 "v1": {
garciadeblasf2af4a12023-01-24 16:56:54 +0100501 "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 },
almagiae47b9132022-05-17 14:12:22 +0200537 }
538 },
tierno701018c2019-06-25 11:13:14 +0000539 "nst": {
540 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100541 "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 },
tierno701018c2019-06-25 11:13:14 +0000575 }
576 },
577 "nsilcm": {
578 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100579 "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 },
tierno701018c2019-06-25 11:13:14 +0000615 }
616 },
617 "nspm": {
618 "v1": {
619 "pm_jobs": {
620 "<ID>": {
621 "reports": {
garciadeblas4568a372021-03-24 09:19:48 +0100622 "<ID>": {
623 "METHODS": ("GET",),
624 "ROLE_PERMISSION": "reports:id:",
625 }
tierno701018c2019-06-25 11:13:14 +0000626 }
627 },
628 },
629 },
630 },
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000631 "nsfm": {
632 "v1": {
garciadeblasf2af4a12023-01-24 16:56:54 +0100633 "alarms": {
634 "METHODS": ("GET", "PATCH"),
635 "ROLE_PERMISSION": "alarms:",
636 "<ID>": {
637 "METHODS": ("GET", "PATCH"),
638 "ROLE_PERMISSION": "alarms:id:",
639 },
640 }
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000641 },
642 },
tierno701018c2019-06-25 11:13:14 +0000643}
644
tiernoc94c3df2018-02-09 15:38:54 +0100645
646class NbiException(Exception):
tiernoc94c3df2018-02-09 15:38:54 +0100647 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
648 Exception.__init__(self, message)
649 self.http_code = http_code
650
651
652class Server(object):
653 instance = 0
654 # to decode bytes to str
655 reader = getreader("utf-8")
656
657 def __init__(self):
658 self.instance += 1
tierno701018c2019-06-25 11:13:14 +0000659 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
delacruzramoad682a52019-12-10 16:26:34 +0100660 self.engine = Engine(self.authenticator)
tiernoc94c3df2018-02-09 15:38:54 +0100661
tiernoc94c3df2018-02-09 15:38:54 +0100662 def _format_in(self, kwargs):
garciadeblasf2af4a12023-01-24 16:56:54 +0100663 error_text = "" # error_text must be initialized outside try
tiernoc94c3df2018-02-09 15:38:54 +0100664 try:
665 indata = None
666 if cherrypy.request.body.length:
667 error_text = "Invalid input format "
668
669 if "Content-Type" in cherrypy.request.headers:
670 if "application/json" in cherrypy.request.headers["Content-Type"]:
671 error_text = "Invalid json format "
672 indata = json.load(self.reader(cherrypy.request.body))
gcalvinode4adfe2018-10-30 11:46:09 +0100673 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100674 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
675 error_text = "Invalid yaml format "
Dario Faccin5584b642023-05-24 10:21:37 +0200676 indata = yaml.safe_load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100677 cherrypy.request.headers.pop("Content-File-MD5", None)
garciadeblas4568a372021-03-24 09:19:48 +0100678 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 ):
tiernof27c79b2018-03-12 17:08:42 +0100685 indata = cherrypy.request.body # .read()
garciadeblas4568a372021-03-24 09:19:48 +0100686 elif (
687 "multipart/form-data"
688 in cherrypy.request.headers["Content-Type"]
689 ):
tiernoc94c3df2018-02-09 15:38:54 +0100690 if "descriptor_file" in kwargs:
691 filecontent = kwargs.pop("descriptor_file")
692 if not filecontent.file:
garciadeblas4568a372021-03-24 09:19:48 +0100693 raise NbiException(
694 "empty file or content", HTTPStatus.BAD_REQUEST
695 )
tiernof27c79b2018-03-12 17:08:42 +0100696 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100697 if filecontent.content_type.value:
garciadeblas4568a372021-03-24 09:19:48 +0100698 cherrypy.request.headers[
699 "Content-Type"
700 ] = filecontent.content_type.value
tiernoc94c3df2018-02-09 15:38:54 +0100701 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 error_text = "Invalid yaml format "
Dario Faccin5584b642023-05-24 10:21:37 +0200706 indata = yaml.safe_load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100707 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100708 else:
709 error_text = "Invalid yaml format "
Dario Faccin5584b642023-05-24 10:21:37 +0200710 indata = yaml.safe_load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100711 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100712 if not indata:
713 indata = {}
714
tiernoc94c3df2018-02-09 15:38:54 +0100715 format_yaml = False
716 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
717 format_yaml = True
718
719 for k, v in kwargs.items():
720 if isinstance(v, str):
721 if v == "":
722 kwargs[k] = None
723 elif format_yaml:
724 try:
Dario Faccin5584b642023-05-24 10:21:37 +0200725 kwargs[k] = yaml.safe_load(v)
tiernoe1281182018-05-22 12:24:36 +0200726 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100727 pass
garciadeblas4568a372021-03-24 09:19:48 +0100728 elif (
729 k.endswith(".gt")
730 or k.endswith(".lt")
731 or k.endswith(".gte")
732 or k.endswith(".lte")
733 ):
tiernoc94c3df2018-02-09 15:38:54 +0100734 try:
735 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200736 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100737 try:
738 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200739 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100740 pass
741 elif v.find(",") > 0:
742 kwargs[k] = v.split(",")
743 elif isinstance(v, (list, tuple)):
744 for index in range(0, len(v)):
745 if v[index] == "":
746 v[index] = None
747 elif format_yaml:
748 try:
Dario Faccin5584b642023-05-24 10:21:37 +0200749 v[index] = yaml.safe_load(v[index])
tiernoe1281182018-05-22 12:24:36 +0200750 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100751 pass
752
tiernof27c79b2018-03-12 17:08:42 +0100753 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100754 except (ValueError, yaml.YAMLError) as exc:
755 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
756 except KeyError as exc:
garciadeblas4568a372021-03-24 09:19:48 +0100757 raise NbiException(
758 "Query string error: " + str(exc), HTTPStatus.BAD_REQUEST
759 )
tiernob92094f2018-05-11 13:44:22 +0200760 except Exception as exc:
761 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100762
763 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000764 def _format_out(data, token_info=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100765 """
766 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100767 :param data: response to be sent. Can be a dict, text or file
tierno701018c2019-06-25 11:13:14 +0000768 :param token_info: Contains among other username and project
tierno5792d7d2019-08-30 15:37:12 +0000769 :param _format: The format to be set as Content-Type if data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100770 :return: None
771 """
tierno0f98af52018-03-19 10:28:22 +0100772 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100773 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100774 if accept and "text/html" in accept:
garciadeblas4568a372021-03-24 09:19:48 +0100775 return html.format(
776 data, cherrypy.request, cherrypy.response, token_info
777 )
tierno09c073e2018-04-26 13:36:48 +0200778 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100779 return
780 elif hasattr(data, "read"): # file object
781 if _format:
782 cherrypy.response.headers["Content-Type"] = _format
783 elif "b" in data.mode: # binariy asssumig zip
garciadeblas4568a372021-03-24 09:19:48 +0100784 cherrypy.response.headers["Content-Type"] = "application/zip"
tiernof27c79b2018-03-12 17:08:42 +0100785 else:
garciadeblas4568a372021-03-24 09:19:48 +0100786 cherrypy.response.headers["Content-Type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100787 # TODO check that cherrypy close file. If not implement pending things to close per thread next
788 return data
tierno0f98af52018-03-19 10:28:22 +0100789 if accept:
Frank Bryden02e700c2020-06-03 13:34:16 +0000790 if "text/html" in accept:
garciadeblas4568a372021-03-24 09:19:48 +0100791 return html.format(
792 data, cherrypy.request, cherrypy.response, token_info
793 )
Frank Brydenb5422da2020-08-10 11:44:11 +0000794 elif "application/yaml" in accept or "*/*" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100795 pass
garciadeblas4568a372021-03-24 09:19:48 +0100796 elif "application/json" in accept or (
797 cherrypy.response.status and cherrypy.response.status >= 300
798 ):
799 cherrypy.response.headers[
800 "Content-Type"
801 ] = "application/json; charset=utf-8"
Frank Bryden02e700c2020-06-03 13:34:16 +0000802 a = json.dumps(data, indent=4) + "\n"
garciadeblas4568a372021-03-24 09:19:48 +0100803 return a.encode("utf8")
804 cherrypy.response.headers["Content-Type"] = "application/yaml"
805 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='"'
tiernoc94c3df2018-02-09 15:38:54 +0100814
815 @cherrypy.expose
816 def index(self, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000817 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100818 try:
819 if cherrypy.request.method == "GET":
tierno701018c2019-06-25 11:13:14 +0000820 token_info = self.authenticator.authorize()
garciadeblas4568a372021-03-24 09:19:48 +0100821 outdata = token_info # Home page
tiernoc94c3df2018-02-09 15:38:54 +0100822 else:
garciadeblas4568a372021-03-24 09:19:48 +0100823 raise cherrypy.HTTPError(
824 HTTPStatus.METHOD_NOT_ALLOWED.value,
825 "Method {} not allowed for tokens".format(cherrypy.request.method),
826 )
tiernoc94c3df2018-02-09 15:38:54 +0100827
tierno701018c2019-06-25 11:13:14 +0000828 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100829
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100830 except (EngineException, AuthException) as e:
tierno701018c2019-06-25 11:13:14 +0000831 # cherrypy.log("index Exception {}".format(e))
tiernoc94c3df2018-02-09 15:38:54 +0100832 cherrypy.response.status = e.http_code.value
tierno701018c2019-06-25 11:13:14 +0000833 return self._format_out("Welcome to OSM!", token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100834
835 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200836 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200837 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200838 try:
839 if cherrypy.request.method != "GET":
garciadeblas4568a372021-03-24 09:19:48 +0100840 raise NbiException(
841 "Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED
842 )
tierno55945e72018-04-06 16:40:27 +0200843 elif args or kwargs:
garciadeblas4568a372021-03-24 09:19:48 +0100844 raise NbiException(
845 "Invalid URL or query string for version",
846 HTTPStatus.METHOD_NOT_ALLOWED,
847 )
tierno9c630112019-08-29 14:21:41 +0000848 # TODO include version of other modules, pick up from some kafka admin message
tierno5792d7d2019-08-30 15:37:12 +0000849 osm_nbi_version = {"version": nbi_version, "date": nbi_version_date}
850 return self._format_out(osm_nbi_version)
tierno55945e72018-04-06 16:40:27 +0200851 except NbiException as e:
852 cherrypy.response.status = e.http_code.value
853 problem_details = {
854 "code": e.http_code.name,
855 "status": e.http_code.value,
856 "detail": str(e),
857 }
858 return self._format_out(problem_details, None)
859
tierno12eac3c2020-03-19 23:22:08 +0000860 def domain(self):
861 try:
862 domains = {
garciadeblas4568a372021-03-24 09:19:48 +0100863 "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 }
tierno12eac3c2020-03-19 23:22:08 +0000870 return self._format_out(domains)
871 except NbiException as e:
872 cherrypy.response.status = e.http_code.value
873 problem_details = {
874 "code": e.http_code.name,
875 "status": e.http_code.value,
876 "detail": str(e),
877 }
878 return self._format_out(problem_details, None)
879
tiernoa5035702019-07-29 08:54:42 +0000880 @staticmethod
881 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 cherrypy.request.login = token_info.get("username", "-")
889 if token_info.get("project_name"):
890 cherrypy.request.login += "/" + token_info["project_name"]
891 if token_info.get("id"):
892 cherrypy.request.login += ";session=" + token_info["id"][0:12]
893
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000894 # NS Fault Management
895 @cherrypy.expose
garciadeblasf2af4a12023-01-24 16:56:54 +0100896 def nsfm(
897 self,
898 version=None,
899 topic=None,
900 uuid=None,
901 project_name=None,
902 ns_id=None,
903 *args,
Patricia Reinoso62fa6732023-02-22 17:57:53 +0000904 **kwargs,
garciadeblasf2af4a12023-01-24 16:56:54 +0100905 ):
906 if topic == "alarms":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000907 try:
908 method = cherrypy.request.method
garciadeblasf2af4a12023-01-24 16:56:54 +0100909 role_permission = self._check_valid_url_method(
910 method, "nsfm", version, topic, None, None, *args
911 )
912 query_string_operations = self._extract_query_string_operations(
913 kwargs, method
914 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000915
garciadeblasf2af4a12023-01-24 16:56:54 +0100916 self.authenticator.authorize(
917 role_permission, query_string_operations, None
918 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000919
920 # to handle get request
garciadeblasf2af4a12023-01-24 16:56:54 +0100921 if cherrypy.request.method == "GET":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000922 # if request is on basis of uuid
garciadeblasf2af4a12023-01-24 16:56:54 +0100923 if uuid and uuid != "None":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000924 try:
925 alarm = self.engine.db.get_one("alarms", {"uuid": uuid})
garciadeblasf2af4a12023-01-24 16:56:54 +0100926 alarm_action = self.engine.db.get_one(
927 "alarms_action", {"uuid": uuid}
928 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000929 alarm.update(alarm_action)
garciadeblasf2af4a12023-01-24 16:56:54 +0100930 vnf = self.engine.db.get_one(
931 "vnfrs", {"nsr-id-ref": alarm["tags"]["ns_id"]}
932 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000933 alarm["vnf-id"] = vnf["_id"]
934 return self._format_out(str(alarm))
935 except Exception:
936 return self._format_out("Please provide valid alarm uuid")
garciadeblasf2af4a12023-01-24 16:56:54 +0100937 elif ns_id and ns_id != "None":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000938 # if request is on basis of ns_id
939 try:
garciadeblasf2af4a12023-01-24 16:56:54 +0100940 alarms = self.engine.db.get_list(
941 "alarms", {"tags.ns_id": ns_id}
942 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000943 for alarm in alarms:
garciadeblasf2af4a12023-01-24 16:56:54 +0100944 alarm_action = self.engine.db.get_one(
945 "alarms_action", {"uuid": alarm["uuid"]}
946 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000947 alarm.update(alarm_action)
948 return self._format_out(str(alarms))
949 except Exception:
950 return self._format_out("Please provide valid ns id")
951 else:
952 # to return only alarm which are related to given project
garciadeblasf2af4a12023-01-24 16:56:54 +0100953 project = self.engine.db.get_one(
954 "projects", {"name": project_name}
955 )
956 project_id = project.get("_id")
957 ns_list = self.engine.db.get_list(
958 "nsrs", {"_admin.projects_read": project_id}
959 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000960 ns_ids = []
961 for ns in ns_list:
962 ns_ids.append(ns.get("_id"))
963 alarms = self.engine.db.get_list("alarms")
garciadeblasf2af4a12023-01-24 16:56:54 +0100964 alarm_list = [
965 alarm
966 for alarm in alarms
967 if alarm["tags"]["ns_id"] in ns_ids
968 ]
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000969 for alrm in alarm_list:
garciadeblasf2af4a12023-01-24 16:56:54 +0100970 action = self.engine.db.get_one(
971 "alarms_action", {"uuid": alrm.get("uuid")}
972 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000973 alrm.update(action)
974 return self._format_out(str(alarm_list))
975 # to handle patch request for alarm update
garciadeblasf2af4a12023-01-24 16:56:54 +0100976 elif cherrypy.request.method == "PATCH":
Dario Faccin5584b642023-05-24 10:21:37 +0200977 data = yaml.safe_load(cherrypy.request.body)
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000978 try:
979 # check if uuid is valid
980 self.engine.db.get_one("alarms", {"uuid": data.get("uuid")})
981 except Exception:
982 return self._format_out("Please provide valid alarm uuid.")
983 if data.get("is_enable") is not None:
984 if data.get("is_enable"):
garciadeblasf2af4a12023-01-24 16:56:54 +0100985 alarm_status = "ok"
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000986 else:
garciadeblasf2af4a12023-01-24 16:56:54 +0100987 alarm_status = "disabled"
988 self.engine.db.set_one(
989 "alarms",
990 {"uuid": data.get("uuid")},
991 {"alarm_status": alarm_status},
992 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000993 else:
garciadeblasf2af4a12023-01-24 16:56:54 +0100994 self.engine.db.set_one(
995 "alarms",
996 {"uuid": data.get("uuid")},
997 {"threshold": data.get("threshold")},
998 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000999 return self._format_out("Alarm updated")
1000 except Exception as e:
garciadeblasf2af4a12023-01-24 16:56:54 +01001001 if isinstance(
1002 e,
1003 (
1004 NbiException,
1005 EngineException,
1006 DbException,
1007 FsException,
1008 MsgException,
1009 AuthException,
1010 ValidationError,
1011 AuthconnException,
1012 ),
1013 ):
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001014 http_code_value = cherrypy.response.status = e.http_code.value
1015 http_code_name = e.http_code.name
1016 cherrypy.log("Exception {}".format(e))
1017 else:
garciadeblasf2af4a12023-01-24 16:56:54 +01001018 http_code_value = (
1019 cherrypy.response.status
1020 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001021 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
1022 http_code_name = HTTPStatus.BAD_REQUEST.name
1023 problem_details = {
1024 "code": http_code_name,
1025 "status": http_code_value,
1026 "detail": str(e),
1027 }
1028 return self._format_out(problem_details)
1029
tierno55945e72018-04-06 16:40:27 +02001030 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +01001031 def token(self, method, token_id=None, kwargs=None):
tierno701018c2019-06-25 11:13:14 +00001032 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +01001033 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +01001034 indata = self._format_in(kwargs)
1035 if not isinstance(indata, dict):
garciadeblas4568a372021-03-24 09:19:48 +01001036 raise NbiException(
1037 "Expected application/yaml or application/json Content-Type",
1038 HTTPStatus.BAD_REQUEST,
1039 )
tiernoa5035702019-07-29 08:54:42 +00001040
1041 if method == "GET":
1042 token_info = self.authenticator.authorize()
1043 # for logging
1044 self._format_login(token_info)
1045 if token_id:
1046 outdata = self.authenticator.get_token(token_info, token_id)
tiernoc94c3df2018-02-09 15:38:54 +01001047 else:
tiernoa5035702019-07-29 08:54:42 +00001048 outdata = self.authenticator.get_token_list(token_info)
1049 elif method == "POST":
1050 try:
1051 token_info = self.authenticator.authorize()
1052 except Exception:
1053 token_info = None
1054 if kwargs:
1055 indata.update(kwargs)
1056 # This is needed to log the user when authentication fails
1057 cherrypy.request.login = "{}".format(indata.get("username", "-"))
garciadeblas4568a372021-03-24 09:19:48 +01001058 outdata = token_info = self.authenticator.new_token(
1059 token_info, indata, cherrypy.request.remote
1060 )
garciadeblasf2af4a12023-01-24 16:56:54 +01001061 cherrypy.session["Authorization"] = outdata["_id"] # pylint: disable=E1101
tiernoa5035702019-07-29 08:54:42 +00001062 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
1063 # for logging
1064 self._format_login(token_info)
selvi.ja9a1fc82022-04-04 06:54:30 +00001065 # password expiry check
1066 if self.authenticator.check_password_expiry(outdata):
garciadeblasf2af4a12023-01-24 16:56:54 +01001067 outdata = {
1068 "id": outdata["id"],
1069 "message": "change_password",
1070 "user_id": outdata["user_id"],
1071 }
tiernoa5035702019-07-29 08:54:42 +00001072 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1073 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1074 elif method == "DELETE":
1075 if not token_id and "id" in kwargs:
1076 token_id = kwargs["id"]
1077 elif not token_id:
1078 token_info = self.authenticator.authorize()
1079 # for logging
1080 self._format_login(token_info)
1081 token_id = token_info["_id"]
1082 outdata = self.authenticator.del_token(token_id)
1083 token_info = None
garciadeblasf2af4a12023-01-24 16:56:54 +01001084 cherrypy.session["Authorization"] = "logout" # pylint: disable=E1101
tiernoa5035702019-07-29 08:54:42 +00001085 # cherrypy.response.cookie["Authorization"] = token_id
1086 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1087 else:
garciadeblas4568a372021-03-24 09:19:48 +01001088 raise NbiException(
1089 "Method {} not allowed for token".format(method),
1090 HTTPStatus.METHOD_NOT_ALLOWED,
1091 )
tiernoa5035702019-07-29 08:54:42 +00001092 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001093
1094 @cherrypy.expose
1095 def test(self, *args, **kwargs):
garciadeblas4568a372021-03-24 09:19:48 +01001096 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 ):
tierno4836bac2020-01-15 14:41:48 +00001100 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value
1101 return "test URL is disabled"
tiernoc94c3df2018-02-09 15:38:54 +01001102 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +01001103 if args and args[0] == "help":
garciadeblas4568a372021-03-24 09:19:48 +01001104 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 )
tiernof27c79b2018-03-12 17:08:42 +01001108
1109 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +01001110 try:
1111 # self.engine.load_dbase(cherrypy.request.app.config)
garciadeblasf2af4a12023-01-24 16:56:54 +01001112 pid = self.authenticator.create_admin_project()
1113 self.authenticator.create_admin_user(pid)
tiernoc94c3df2018-02-09 15:38:54 +01001114 return "Done. User 'admin', password 'admin' created"
1115 except Exception:
1116 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1117 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +01001118 elif args and args[0] == "file":
garciadeblas4568a372021-03-24 09:19:48 +01001119 return cherrypy.lib.static.serve_file(
1120 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1],
1121 "text/plain",
1122 "attachment",
1123 )
tiernof27c79b2018-03-12 17:08:42 +01001124 elif args and args[0] == "file2":
garciadeblas4568a372021-03-24 09:19:48 +01001125 f_path = (
1126 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1]
1127 )
tiernof27c79b2018-03-12 17:08:42 +01001128 f = open(f_path, "r")
1129 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +01001130 return f
tierno55945e72018-04-06 16:40:27 +02001131
tiernof27c79b2018-03-12 17:08:42 +01001132 elif len(args) == 2 and args[0] == "db-clear":
tiernof717cbe2018-12-03 16:35:42 +00001133 deleted_info = self.engine.db.del_list(args[1], kwargs)
1134 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
1135 elif len(args) and args[0] == "fs-clear":
1136 if len(args) >= 2:
1137 folders = (args[1],)
1138 else:
1139 folders = self.engine.fs.dir_ls(".")
1140 for folder in folders:
1141 self.engine.fs.file_delete(folder)
1142 return ",".join(folders) + " folders deleted\n"
tiernoc94c3df2018-02-09 15:38:54 +01001143 elif args and args[0] == "login":
1144 if not cherrypy.request.headers.get("Authorization"):
garciadeblas4568a372021-03-24 09:19:48 +01001145 cherrypy.response.headers[
1146 "WWW-Authenticate"
1147 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
tiernoc94c3df2018-02-09 15:38:54 +01001148 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1149 elif args and args[0] == "login2":
1150 if not cherrypy.request.headers.get("Authorization"):
garciadeblas4568a372021-03-24 09:19:48 +01001151 cherrypy.response.headers[
1152 "WWW-Authenticate"
1153 ] = 'Bearer realm="Access to OSM site"'
tiernoc94c3df2018-02-09 15:38:54 +01001154 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1155 elif args and args[0] == "sleep":
1156 sleep_time = 5
1157 try:
1158 sleep_time = int(args[1])
1159 except Exception:
1160 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1161 return self._format_out("Database already initialized")
1162 thread_info = cherrypy.thread_data
1163 print(thread_info)
1164 time.sleep(sleep_time)
1165 # thread_info
1166 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +02001167 main_topic = args[1]
1168 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +01001169 try:
garciadeblas4568a372021-03-24 09:19:48 +01001170 if cherrypy.request.method == "POST":
Dario Faccin5584b642023-05-24 10:21:37 +02001171 to_send = yaml.safe_load(cherrypy.request.body)
tierno55945e72018-04-06 16:40:27 +02001172 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +02001173 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +02001174 return_text += " {}: {}\n".format(k, v)
garciadeblas4568a372021-03-24 09:19:48 +01001175 elif cherrypy.request.method == "GET":
tierno55945e72018-04-06 16:40:27 +02001176 for k, v in kwargs.items():
Dario Faccin5584b642023-05-24 10:21:37 +02001177 v_dict = yaml.safe_load(v)
tiernof1509b22020-05-12 14:32:37 +00001178 self.engine.msg.write(main_topic, k, v_dict)
1179 return_text += " {}: {}\n".format(k, v_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001180 except Exception as e:
tierno55945e72018-04-06 16:40:27 +02001181 return_text += "Error: " + str(e)
1182 return_text += "</pre></html>\n"
1183 return return_text
tiernoc94c3df2018-02-09 15:38:54 +01001184
1185 return_text = (
garciadeblas4568a372021-03-24 09:19:48 +01001186 "<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)
garciadeblasf2af4a12023-01-24 16:56:54 +01001191 + " session: {}\n".format(cherrypy.session) # pylint: disable=E1101
garciadeblas4568a372021-03-24 09:19:48 +01001192 + " cookie: {}\n".format(cherrypy.request.cookie)
1193 + " method: {}\n".format(cherrypy.request.method)
garciadeblasf2af4a12023-01-24 16:56:54 +01001194 + " session: {}\n".format(
1195 cherrypy.session.get("fieldname") # pylint: disable=E1101
1196 )
garciadeblas4568a372021-03-24 09:19:48 +01001197 + " body:\n"
1198 )
tiernoc94c3df2018-02-09 15:38:54 +01001199 return_text += " length: {}\n".format(cherrypy.request.body.length)
1200 if cherrypy.request.body.length:
1201 return_text += " content: {}\n".format(
garciadeblas4568a372021-03-24 09:19:48 +01001202 str(
1203 cherrypy.request.body.read(
1204 int(cherrypy.request.headers.get("Content-Length", 0))
1205 )
1206 )
1207 )
tiernoc94c3df2018-02-09 15:38:54 +01001208 if thread_info:
1209 return_text += "thread: {}\n".format(thread_info)
1210 return_text += "</pre></html>"
1211 return return_text
1212
tierno701018c2019-06-25 11:13:14 +00001213 @staticmethod
1214 def _check_valid_url_method(method, *args):
tiernof27c79b2018-03-12 17:08:42 +01001215 if len(args) < 3:
garciadeblas4568a372021-03-24 09:19:48 +01001216 raise NbiException(
1217 "URL must contain at least 'main_topic/version/topic'",
1218 HTTPStatus.METHOD_NOT_ALLOWED,
1219 )
tiernof27c79b2018-03-12 17:08:42 +01001220
tierno701018c2019-06-25 11:13:14 +00001221 reference = valid_url_methods
tiernof27c79b2018-03-12 17:08:42 +01001222 for arg in args:
1223 if arg is None:
1224 break
1225 if not isinstance(reference, dict):
garciadeblas4568a372021-03-24 09:19:48 +01001226 raise NbiException(
1227 "URL contains unexpected extra items '{}'".format(arg),
1228 HTTPStatus.METHOD_NOT_ALLOWED,
1229 )
tiernof27c79b2018-03-12 17:08:42 +01001230
1231 if arg in reference:
1232 reference = reference[arg]
1233 elif "<ID>" in reference:
1234 reference = reference["<ID>"]
1235 elif "*" in reference:
tierno74b53582020-06-18 10:52:37 +00001236 # if there is content
1237 if reference["*"]:
1238 reference = reference["*"]
tiernof27c79b2018-03-12 17:08:42 +01001239 break
1240 else:
garciadeblas4568a372021-03-24 09:19:48 +01001241 raise NbiException(
1242 "Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED
1243 )
tiernof27c79b2018-03-12 17:08:42 +01001244 if "TODO" in reference and method in reference["TODO"]:
garciadeblas4568a372021-03-24 09:19:48 +01001245 raise NbiException(
1246 "Method {} not supported yet for this URL".format(method),
1247 HTTPStatus.NOT_IMPLEMENTED,
1248 )
tierno2236d202018-05-16 19:05:16 +02001249 elif "METHODS" in reference and method not in reference["METHODS"]:
garciadeblas4568a372021-03-24 09:19:48 +01001250 raise NbiException(
1251 "Method {} not supported for this URL".format(method),
1252 HTTPStatus.METHOD_NOT_ALLOWED,
1253 )
tierno701018c2019-06-25 11:13:14 +00001254 return reference["ROLE_PERMISSION"] + method.lower()
tiernof27c79b2018-03-12 17:08:42 +01001255
1256 @staticmethod
tiernob24258a2018-10-04 18:39:49 +02001257 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +01001258 """
1259 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +02001260 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +01001261 :param version:
tiernob24258a2018-10-04 18:39:49 +02001262 :param topic:
tiernof27c79b2018-03-12 17:08:42 +01001263 :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
garciadeblas4568a372021-03-24 09:19:48 +01001267 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(
1268 main_topic, version, topic, id
1269 )
tiernof27c79b2018-03-12 17:08:42 +01001270 return
1271
tierno65ca36d2019-02-12 19:27:52 +01001272 @staticmethod
tierno701018c2019-06-25 11:13:14 +00001273 def _extract_query_string_operations(kwargs, method):
1274 """
1275
1276 :param kwargs:
1277 :return:
1278 """
1279 query_string_operations = []
1280 if kwargs:
1281 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1282 if qs in kwargs and kwargs[qs].lower() != "false":
1283 query_string_operations.append(qs.lower() + ":" + method.lower())
1284 return query_string_operations
1285
1286 @staticmethod
1287 def _manage_admin_query(token_info, kwargs, method, _id):
tierno65ca36d2019-02-12 19:27:52 +01001288 """
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
tierno701018c2019-06-25 11:13:14 +00001291 :param token_info: token_info rights obtained by token
tierno65ca36d2019-02-12 19:27:52 +01001292 :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 """
garciadeblas4568a372021-03-24 09:19:48 +01001302 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 }
tierno65ca36d2019-02-12 19:27:52 +01001310 if kwargs:
1311 # FORCE
1312 if "FORCE" in kwargs:
garciadeblas4568a372021-03-24 09:19:48 +01001313 if (
1314 kwargs["FORCE"].lower() != "false"
1315 ): # if None or True set force to True
tierno65ca36d2019-02-12 19:27:52 +01001316 admin_query["force"] = True
1317 del kwargs["FORCE"]
1318 # PUBLIC
1319 if "PUBLIC" in kwargs:
garciadeblas4568a372021-03-24 09:19:48 +01001320 if (
1321 kwargs["PUBLIC"].lower() != "false"
1322 ): # if None or True set public to True
tierno65ca36d2019-02-12 19:27:52 +01001323 admin_query["public"] = True
1324 else:
1325 admin_query["public"] = False
1326 del kwargs["PUBLIC"]
1327 # ADMIN
1328 if "ADMIN" in kwargs:
1329 behave_as = kwargs.pop("ADMIN")
1330 if behave_as.lower() != "false":
tierno701018c2019-06-25 11:13:14 +00001331 if not token_info["admin"]:
garciadeblas4568a372021-03-24 09:19:48 +01001332 raise NbiException(
1333 "Only admin projects can use 'ADMIN' query string",
1334 HTTPStatus.UNAUTHORIZED,
1335 )
1336 if (
1337 not behave_as or behave_as.lower() == "true"
1338 ): # convert True, None to empty list
tierno65ca36d2019-02-12 19:27:52 +01001339 admin_query["project_id"] = ()
1340 elif isinstance(behave_as, (list, tuple)):
1341 admin_query["project_id"] = behave_as
garciadeblas4568a372021-03-24 09:19:48 +01001342 else: # isinstance(behave_as, str)
1343 admin_query["project_id"] = (behave_as,)
tierno65ca36d2019-02-12 19:27:52 +01001344 if "SET_PROJECT" in kwargs:
1345 set_project = kwargs.pop("SET_PROJECT")
1346 if not set_project:
1347 admin_query["set_project"] = list(admin_query["project_id"])
1348 else:
1349 if isinstance(set_project, str):
garciadeblas4568a372021-03-24 09:19:48 +01001350 set_project = (set_project,)
tierno65ca36d2019-02-12 19:27:52 +01001351 if admin_query["project_id"]:
1352 for p in set_project:
1353 if p not in admin_query["project_id"]:
garciadeblas4568a372021-03-24 09:19:48 +01001354 raise NbiException(
1355 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1356 "'ADMIN='{p}'".format(p=p),
1357 HTTPStatus.UNAUTHORIZED,
1358 )
tierno65ca36d2019-02-12 19:27:52 +01001359 admin_query["set_project"] = set_project
1360
1361 # PROJECT_READ
1362 # if "PROJECT_READ" in kwargs:
1363 # admin_query["project"] = kwargs.pop("project")
tierno701018c2019-06-25 11:13:14 +00001364 # if admin_query["project"] == token_info["project_id"]:
tierno65ca36d2019-02-12 19:27:52 +01001365 if method == "GET":
1366 if _id:
1367 admin_query["method"] = "show"
1368 else:
1369 admin_query["method"] = "list"
1370 elif method == "DELETE":
1371 admin_query["method"] = "delete"
1372 else:
1373 admin_query["method"] = "write"
1374 return admin_query
1375
tiernoc94c3df2018-02-09 15:38:54 +01001376 @cherrypy.expose
garciadeblas4568a372021-03-24 09:19:48 +01001377 def default(
1378 self,
1379 main_topic=None,
1380 version=None,
1381 topic=None,
1382 _id=None,
1383 item=None,
1384 *args,
Patricia Reinoso62fa6732023-02-22 17:57:53 +00001385 **kwargs,
garciadeblas4568a372021-03-24 09:19:48 +01001386 ):
tierno701018c2019-06-25 11:13:14 +00001387 token_info = None
tiernof27c79b2018-03-12 17:08:42 +01001388 outdata = None
1389 _format = None
tierno0f98af52018-03-19 10:28:22 +01001390 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +02001391 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +02001392 rollback = []
tierno701018c2019-06-25 11:13:14 +00001393 engine_session = None
tiernoc94c3df2018-02-09 15:38:54 +01001394 try:
tiernob24258a2018-10-04 18:39:49 +02001395 if not main_topic or not version or not topic:
garciadeblas4568a372021-03-24 09:19:48 +01001396 raise NbiException(
1397 "URL must contain at least 'main_topic/version/topic'",
1398 HTTPStatus.METHOD_NOT_ALLOWED,
1399 )
1400 if main_topic not in (
1401 "admin",
1402 "vnfpkgm",
1403 "nsd",
1404 "nslcm",
1405 "pdu",
1406 "nst",
1407 "nsilcm",
1408 "nspm",
almagiae47b9132022-05-17 14:12:22 +02001409 "vnflcm",
garciadeblas4568a372021-03-24 09:19:48 +01001410 ):
1411 raise NbiException(
1412 "URL main_topic '{}' not supported".format(main_topic),
1413 HTTPStatus.METHOD_NOT_ALLOWED,
1414 )
1415 if version != "v1":
1416 raise NbiException(
1417 "URL version '{}' not supported".format(version),
1418 HTTPStatus.METHOD_NOT_ALLOWED,
1419 )
tiernoc94c3df2018-02-09 15:38:54 +01001420
garciadeblas4568a372021-03-24 09:19:48 +01001421 if (
1422 kwargs
1423 and "METHOD" in kwargs
1424 and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1425 ):
tiernof27c79b2018-03-12 17:08:42 +01001426 method = kwargs.pop("METHOD")
1427 else:
1428 method = cherrypy.request.method
tierno65ca36d2019-02-12 19:27:52 +01001429
garciadeblas4568a372021-03-24 09:19:48 +01001430 role_permission = self._check_valid_url_method(
1431 method, main_topic, version, topic, _id, item, *args
1432 )
1433 query_string_operations = self._extract_query_string_operations(
1434 kwargs, method
1435 )
tiernob24258a2018-10-04 18:39:49 +02001436 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +01001437 return self.token(method, _id, kwargs)
garciadeblas4568a372021-03-24 09:19:48 +01001438 token_info = self.authenticator.authorize(
1439 role_permission, query_string_operations, _id
1440 )
tierno12eac3c2020-03-19 23:22:08 +00001441 if main_topic == "admin" and topic == "domains":
1442 return self.domain()
tierno701018c2019-06-25 11:13:14 +00001443 engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
tiernof27c79b2018-03-12 17:08:42 +01001444 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +02001445 engine_topic = topic
preethika.p329b8182020-04-22 12:25:39 +05301446
vijay.r35ef2f72019-04-30 17:55:49 +05301447 if item and topic != "pm_jobs":
tiernob24258a2018-10-04 18:39:49 +02001448 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +01001449
tiernob24258a2018-10-04 18:39:49 +02001450 if main_topic == "nsd":
1451 engine_topic = "nsds"
1452 elif main_topic == "vnfpkgm":
1453 engine_topic = "vnfds"
delacruzramo271d2002019-12-02 21:00:37 +01001454 if topic == "vnfpkg_op_occs":
1455 engine_topic = "vnfpkgops"
1456 if topic == "vnf_packages" and item == "action":
1457 engine_topic = "vnfpkgops"
tiernob24258a2018-10-04 18:39:49 +02001458 elif main_topic == "nslcm":
1459 engine_topic = "nsrs"
1460 if topic == "ns_lcm_op_occs":
1461 engine_topic = "nslcmops"
1462 if topic == "vnfrs" or topic == "vnf_instances":
1463 engine_topic = "vnfrs"
almagiae47b9132022-05-17 14:12:22 +02001464 elif main_topic == "vnflcm":
1465 if topic == "vnf_lcm_op_occs":
1466 engine_topic = "vnflcmops"
garciadeblas9750c5a2018-10-15 16:20:35 +02001467 elif main_topic == "nst":
1468 engine_topic = "nsts"
1469 elif main_topic == "nsilcm":
1470 engine_topic = "nsis"
1471 if topic == "nsi_lcm_op_occs":
delacruzramoc061f562019-04-05 11:00:02 +02001472 engine_topic = "nsilcmops"
tiernob24258a2018-10-04 18:39:49 +02001473 elif main_topic == "pdu":
1474 engine_topic = "pdus"
garciadeblas4568a372021-03-24 09:19:48 +01001475 if (
1476 engine_topic == "vims"
1477 ): # TODO this is for backward compatibility, it will be removed in the future
tiernob24258a2018-10-04 18:39:49 +02001478 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +01001479
preethika.p329b8182020-04-22 12:25:39 +05301480 if topic == "subscriptions":
1481 engine_topic = main_topic + "_" + topic
1482
tiernoc94c3df2018-02-09 15:38:54 +01001483 if method == "GET":
garciadeblas4568a372021-03-24 09:19:48 +01001484 if item in (
1485 "nsd_content",
1486 "package_content",
1487 "artifacts",
1488 "vnfd",
1489 "nsd",
1490 "nst",
1491 "nst_content",
1492 ):
garciadeblas9750c5a2018-10-15 16:20:35 +02001493 if item in ("vnfd", "nsd", "nst"):
tiernof27c79b2018-03-12 17:08:42 +01001494 path = "$DESCRIPTOR"
1495 elif args:
1496 path = args
tiernob24258a2018-10-04 18:39:49 +02001497 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +01001498 path = ()
1499 else:
1500 path = None
garciadeblas4568a372021-03-24 09:19:48 +01001501 file, _format = self.engine.get_file(
1502 engine_session,
1503 engine_topic,
1504 _id,
1505 path,
1506 cherrypy.request.headers.get("Accept"),
1507 )
tiernof27c79b2018-03-12 17:08:42 +01001508 outdata = file
1509 elif not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001510 outdata = self.engine.get_item_list(
1511 engine_session, engine_topic, kwargs, api_req=True
1512 )
tiernoc94c3df2018-02-09 15:38:54 +01001513 else:
vijay.r35ef2f72019-04-30 17:55:49 +05301514 if item == "reports":
1515 # TODO check that project_id (_id in this context) has permissions
1516 _id = args[0]
K Sai Kiran57589552021-01-27 21:38:34 +05301517 filter_q = None
1518 if "vcaStatusRefresh" in kwargs:
1519 filter_q = {"vcaStatusRefresh": kwargs["vcaStatusRefresh"]}
garciadeblasf2af4a12023-01-24 16:56:54 +01001520 outdata = self.engine.get_item(
1521 engine_session, engine_topic, _id, filter_q, True
1522 )
delacruzramo271d2002019-12-02 21:00:37 +01001523
tiernof27c79b2018-03-12 17:08:42 +01001524 elif method == "POST":
tiernobdebce92019-07-01 15:36:49 +00001525 cherrypy.response.status = HTTPStatus.CREATED.value
garciadeblas4568a372021-03-24 09:19:48 +01001526 if topic in (
1527 "ns_descriptors_content",
1528 "vnf_packages_content",
1529 "netslice_templates_content",
1530 ):
tiernof27c79b2018-03-12 17:08:42 +01001531 _id = cherrypy.request.headers.get("Transaction-Id")
1532 if not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001533 _id, _ = self.engine.new_item(
1534 rollback,
1535 engine_session,
1536 engine_topic,
1537 {},
1538 None,
1539 cherrypy.request.headers,
1540 )
1541 completed = self.engine.upload_content(
1542 engine_session,
1543 engine_topic,
1544 _id,
1545 indata,
1546 kwargs,
1547 cherrypy.request.headers,
1548 )
tiernof27c79b2018-03-12 17:08:42 +01001549 if completed:
tiernob24258a2018-10-04 18:39:49 +02001550 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001551 else:
1552 cherrypy.response.headers["Transaction-Id"] = _id
1553 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +02001554 elif topic == "ns_instances_content":
1555 # creates NSR
garciadeblas4568a372021-03-24 09:19:48 +01001556 _id, _ = self.engine.new_item(
1557 rollback, engine_session, engine_topic, indata, kwargs
1558 )
tiernob24258a2018-10-04 18:39:49 +02001559 # creates nslcmop
1560 indata["lcmOperationType"] = "instantiate"
1561 indata["nsInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001562 nslcmop_id, _ = self.engine.new_item(
1563 rollback, engine_session, "nslcmops", indata, None
1564 )
tiernob24258a2018-10-04 18:39:49 +02001565 self._set_location_header(main_topic, version, topic, _id)
kuuse078f55e2019-05-16 19:24:21 +02001566 outdata = {"id": _id, "nslcmop_id": nslcmop_id}
tiernob24258a2018-10-04 18:39:49 +02001567 elif topic == "ns_instances" and item:
1568 indata["lcmOperationType"] = item
1569 indata["nsInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001570 _id, _ = self.engine.new_item(
1571 rollback, engine_session, "nslcmops", indata, kwargs
1572 )
1573 self._set_location_header(
1574 main_topic, version, "ns_lcm_op_occs", _id
1575 )
tierno65acb4d2018-04-06 16:42:40 +02001576 outdata = {"id": _id}
1577 cherrypy.response.status = HTTPStatus.ACCEPTED.value
garciadeblas9750c5a2018-10-15 16:20:35 +02001578 elif topic == "netslice_instances_content":
Felipe Vicens07f31722018-10-29 15:16:44 +01001579 # creates NetSlice_Instance_record (NSIR)
garciadeblas4568a372021-03-24 09:19:48 +01001580 _id, _ = self.engine.new_item(
1581 rollback, engine_session, engine_topic, indata, kwargs
1582 )
Felipe Vicens07f31722018-10-29 15:16:44 +01001583 self._set_location_header(main_topic, version, topic, _id)
garciadeblas9750c5a2018-10-15 16:20:35 +02001584 indata["lcmOperationType"] = "instantiate"
Felipe Vicens126af572019-06-05 19:13:04 +02001585 indata["netsliceInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001586 nsilcmop_id, _ = self.engine.new_item(
1587 rollback, engine_session, "nsilcmops", indata, kwargs
1588 )
kuuse078f55e2019-05-16 19:24:21 +02001589 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
garciadeblas9750c5a2018-10-15 16:20:35 +02001590 elif topic == "netslice_instances" and item:
1591 indata["lcmOperationType"] = item
Felipe Vicens126af572019-06-05 19:13:04 +02001592 indata["netsliceInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001593 _id, _ = self.engine.new_item(
1594 rollback, engine_session, "nsilcmops", indata, kwargs
1595 )
1596 self._set_location_header(
1597 main_topic, version, "nsi_lcm_op_occs", _id
1598 )
garciadeblas9750c5a2018-10-15 16:20:35 +02001599 outdata = {"id": _id}
1600 cherrypy.response.status = HTTPStatus.ACCEPTED.value
delacruzramo271d2002019-12-02 21:00:37 +01001601 elif topic == "vnf_packages" and item == "action":
1602 indata["lcmOperationType"] = item
1603 indata["vnfPkgId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001604 _id, _ = self.engine.new_item(
1605 rollback, engine_session, "vnfpkgops", indata, kwargs
1606 )
1607 self._set_location_header(
1608 main_topic, version, "vnfpkg_op_occs", _id
1609 )
delacruzramo271d2002019-12-02 21:00:37 +01001610 outdata = {"id": _id}
1611 cherrypy.response.status = HTTPStatus.ACCEPTED.value
preethika.p329b8182020-04-22 12:25:39 +05301612 elif topic == "subscriptions":
garciadeblas4568a372021-03-24 09:19:48 +01001613 _id, _ = self.engine.new_item(
1614 rollback, engine_session, engine_topic, indata, kwargs
1615 )
preethika.p329b8182020-04-22 12:25:39 +05301616 self._set_location_header(main_topic, version, topic, _id)
1617 link = {}
1618 link["self"] = cherrypy.response.headers["Location"]
garciadeblas4568a372021-03-24 09:19:48 +01001619 outdata = {
1620 "id": _id,
1621 "filter": indata["filter"],
1622 "callbackUri": indata["CallbackUri"],
1623 "_links": link,
1624 }
preethika.p329b8182020-04-22 12:25:39 +05301625 cherrypy.response.status = HTTPStatus.CREATED.value
almagiae47b9132022-05-17 14:12:22 +02001626 elif topic == "vnf_instances" and item:
1627 indata["lcmOperationType"] = item
1628 indata["vnfInstanceId"] = _id
garciadeblasf2af4a12023-01-24 16:56:54 +01001629 _id, _ = self.engine.new_item(
1630 rollback, engine_session, "vnflcmops", indata, kwargs
1631 )
1632 self._set_location_header(
1633 main_topic, version, "vnf_lcm_op_occs", _id
1634 )
almagiae47b9132022-05-17 14:12:22 +02001635 outdata = {"id": _id}
1636 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +01001637 else:
garciadeblas4568a372021-03-24 09:19:48 +01001638 _id, op_id = self.engine.new_item(
1639 rollback,
1640 engine_session,
1641 engine_topic,
1642 indata,
1643 kwargs,
1644 cherrypy.request.headers,
1645 )
tiernob24258a2018-10-04 18:39:49 +02001646 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001647 outdata = {"id": _id}
tiernobdebce92019-07-01 15:36:49 +00001648 if op_id:
1649 outdata["op_id"] = op_id
1650 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernob24258a2018-10-04 18:39:49 +02001651 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tierno09c073e2018-04-26 13:36:48 +02001652
tiernoc94c3df2018-02-09 15:38:54 +01001653 elif method == "DELETE":
1654 if not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001655 outdata = self.engine.del_item_list(
1656 engine_session, engine_topic, kwargs
1657 )
tierno09c073e2018-04-26 13:36:48 +02001658 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +01001659 else: # len(args) > 1
tierno22577432020-04-08 15:16:57 +00001660 # for NS NSI generate an operation
1661 op_id = None
tierno701018c2019-06-25 11:13:14 +00001662 if topic == "ns_instances_content" and not engine_session["force"]:
tiernob24258a2018-10-04 18:39:49 +02001663 nslcmop_desc = {
1664 "lcmOperationType": "terminate",
1665 "nsInstanceId": _id,
garciadeblas4568a372021-03-24 09:19:48 +01001666 "autoremove": True,
tiernob24258a2018-10-04 18:39:49 +02001667 }
garciadeblas4568a372021-03-24 09:19:48 +01001668 op_id, _ = self.engine.new_item(
1669 rollback, engine_session, "nslcmops", nslcmop_desc, kwargs
1670 )
tierno22577432020-04-08 15:16:57 +00001671 if op_id:
1672 outdata = {"_id": op_id}
garciadeblas4568a372021-03-24 09:19:48 +01001673 elif (
1674 topic == "netslice_instances_content"
1675 and not engine_session["force"]
1676 ):
garciadeblas9750c5a2018-10-15 16:20:35 +02001677 nsilcmop_desc = {
1678 "lcmOperationType": "terminate",
Felipe Vicens126af572019-06-05 19:13:04 +02001679 "netsliceInstanceId": _id,
garciadeblas4568a372021-03-24 09:19:48 +01001680 "autoremove": True,
garciadeblas9750c5a2018-10-15 16:20:35 +02001681 }
garciadeblas4568a372021-03-24 09:19:48 +01001682 op_id, _ = self.engine.new_item(
1683 rollback, engine_session, "nsilcmops", nsilcmop_desc, None
1684 )
tierno22577432020-04-08 15:16:57 +00001685 if op_id:
1686 outdata = {"_id": op_id}
1687 # if there is not any deletion in process, delete
1688 if not op_id:
1689 op_id = self.engine.del_item(engine_session, engine_topic, _id)
1690 if op_id:
1691 outdata = {"op_id": op_id}
garciadeblas4568a372021-03-24 09:19:48 +01001692 cherrypy.response.status = (
1693 HTTPStatus.ACCEPTED.value
1694 if op_id
1695 else HTTPStatus.NO_CONTENT.value
1696 )
tierno09c073e2018-04-26 13:36:48 +02001697
tierno7ae10112018-05-18 14:36:02 +02001698 elif method in ("PUT", "PATCH"):
tiernobdebce92019-07-01 15:36:49 +00001699 op_id = None
tierno701018c2019-06-25 11:13:14 +00001700 if not indata and not kwargs and not engine_session.get("set_project"):
garciadeblas4568a372021-03-24 09:19:48 +01001701 raise NbiException(
1702 "Nothing to update. Provide payload and/or query string",
1703 HTTPStatus.BAD_REQUEST,
1704 )
1705 if (
1706 item in ("nsd_content", "package_content", "nst_content")
1707 and method == "PUT"
1708 ):
1709 completed = self.engine.upload_content(
1710 engine_session,
1711 engine_topic,
1712 _id,
1713 indata,
1714 kwargs,
1715 cherrypy.request.headers,
1716 )
tiernof27c79b2018-03-12 17:08:42 +01001717 if not completed:
1718 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +01001719 else:
garciadeblas4568a372021-03-24 09:19:48 +01001720 op_id = self.engine.edit_item(
1721 engine_session, engine_topic, _id, indata, kwargs
1722 )
tiernobdebce92019-07-01 15:36:49 +00001723
1724 if op_id:
1725 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1726 outdata = {"op_id": op_id}
1727 else:
1728 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
1729 outdata = None
tiernoc94c3df2018-02-09 15:38:54 +01001730 else:
garciadeblas4568a372021-03-24 09:19:48 +01001731 raise NbiException(
1732 "Method {} not allowed".format(method),
1733 HTTPStatus.METHOD_NOT_ALLOWED,
1734 )
tiernoa6bb45d2019-06-14 09:45:39 +00001735
1736 # if Role information changes, it is needed to reload the information of roles
1737 if topic == "roles" and method != "GET":
1738 self.authenticator.load_operation_to_allowed_roles()
delacruzramoad682a52019-12-10 16:26:34 +01001739
garciadeblas4568a372021-03-24 09:19:48 +01001740 if (
1741 topic == "projects"
1742 and method == "DELETE"
1743 or topic in ["users", "roles"]
1744 and method in ["PUT", "PATCH", "DELETE"]
1745 ):
delacruzramoad682a52019-12-10 16:26:34 +01001746 self.authenticator.remove_token_from_cache()
1747
tierno701018c2019-06-25 11:13:14 +00001748 return self._format_out(outdata, token_info, _format)
tiernob24258a2018-10-04 18:39:49 +02001749 except Exception as e:
garciadeblas4568a372021-03-24 09:19:48 +01001750 if isinstance(
1751 e,
1752 (
1753 NbiException,
1754 EngineException,
1755 DbException,
1756 FsException,
1757 MsgException,
1758 AuthException,
1759 ValidationError,
1760 AuthconnException,
1761 ),
1762 ):
tiernob24258a2018-10-04 18:39:49 +02001763 http_code_value = cherrypy.response.status = e.http_code.value
1764 http_code_name = e.http_code.name
1765 cherrypy.log("Exception {}".format(e))
1766 else:
garciadeblas4568a372021-03-24 09:19:48 +01001767 http_code_value = (
1768 cherrypy.response.status
1769 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Felipe Vicense36ab852018-11-23 14:12:09 +01001770 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
tiernob24258a2018-10-04 18:39:49 +02001771 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +02001772 if hasattr(outdata, "close"): # is an open file
1773 outdata.close()
tiernob24258a2018-10-04 18:39:49 +02001774 error_text = str(e)
1775 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +02001776 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +02001777 try:
tiernocc103432018-10-19 14:10:35 +02001778 if rollback_item.get("operation") == "set":
garciadeblas4568a372021-03-24 09:19:48 +01001779 self.engine.db.set_one(
1780 rollback_item["topic"],
1781 {"_id": rollback_item["_id"]},
1782 rollback_item["content"],
1783 fail_on_empty=False,
1784 )
preethika.p329b8182020-04-22 12:25:39 +05301785 elif rollback_item.get("operation") == "del_list":
garciadeblas4568a372021-03-24 09:19:48 +01001786 self.engine.db.del_list(
1787 rollback_item["topic"],
1788 rollback_item["filter"],
garciadeblas4568a372021-03-24 09:19:48 +01001789 )
tiernocc103432018-10-19 14:10:35 +02001790 else:
garciadeblas4568a372021-03-24 09:19:48 +01001791 self.engine.db.del_one(
1792 rollback_item["topic"],
1793 {"_id": rollback_item["_id"]},
1794 fail_on_empty=False,
1795 )
tierno3ace63c2018-05-03 17:51:43 +02001796 except Exception as e2:
garciadeblas4568a372021-03-24 09:19:48 +01001797 rollback_error_text = "Rollback Exception {}: {}".format(
1798 rollback_item, e2
1799 )
tiernob24258a2018-10-04 18:39:49 +02001800 cherrypy.log(rollback_error_text)
1801 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)
tiernoc94c3df2018-02-09 15:38:54 +01001805 problem_details = {
tiernob24258a2018-10-04 18:39:49 +02001806 "code": http_code_name,
1807 "status": http_code_value,
1808 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +01001809 }
tierno701018c2019-06-25 11:13:14 +00001810 return self._format_out(problem_details, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001811 # raise cherrypy.HTTPError(e.http_code.value, str(e))
tiernoa5035702019-07-29 08:54:42 +00001812 finally:
1813 if token_info:
1814 self._format_login(token_info)
1815 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
1816 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1817 if outdata.get(logging_id):
garciadeblas4568a372021-03-24 09:19:48 +01001818 cherrypy.request.login += ";{}={}".format(
1819 logging_id, outdata[logging_id][:36]
1820 )
tiernoc94c3df2018-02-09 15:38:54 +01001821
1822
tiernoc94c3df2018-02-09 15:38:54 +01001823def _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 """
tierno932499c2019-01-28 17:28:10 +00001830 global nbi_server
1831 global subscription_thread
tiernoc94c3df2018-02-09 15:38:54 +01001832 cherrypy.log.error("Starting osm_nbi")
1833 # update general cherrypy configuration
1834 update_dict = {}
1835
garciadeblas4568a372021-03-24 09:19:48 +01001836 engine_config = cherrypy.tree.apps["/osm"].config
tiernoc94c3df2018-02-09 15:38:54 +01001837 for k, v in environ.items():
1838 if not k.startswith("OSMNBI_"):
1839 continue
tiernoe1281182018-05-22 12:24:36 +02001840 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +01001841 if not k2:
1842 continue
1843 try:
1844 # update static configuration
garciadeblas4568a372021-03-24 09:19:48 +01001845 if k == "OSMNBI_STATIC_DIR":
1846 engine_config["/static"]["tools.staticdir.dir"] = v
1847 engine_config["/static"]["tools.staticdir.on"] = True
1848 elif k == "OSMNBI_SOCKET_PORT" or k == "OSMNBI_SERVER_PORT":
1849 update_dict["server.socket_port"] = int(v)
1850 elif k == "OSMNBI_SOCKET_HOST" or k == "OSMNBI_SERVER_HOST":
1851 update_dict["server.socket_host"] = v
tiernof5298be2018-05-16 14:43:57 +02001852 elif k1 in ("server", "test", "auth", "log"):
garciadeblas4568a372021-03-24 09:19:48 +01001853 update_dict[k1 + "." + k2] = v
Patricia Reinoso62fa6732023-02-22 17:57:53 +00001854 elif k1 in ("message", "database", "storage", "authentication", "temporal"):
tiernof5298be2018-05-16 14:43:57 +02001855 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001856 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +01001857 engine_config[k1][k2] = int(v)
1858 else:
1859 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001860
tiernoc94c3df2018-02-09 15:38:54 +01001861 except ValueError as e:
1862 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1863 except Exception as e:
garciadeblasf2af4a12023-01-24 16:56:54 +01001864 cherrypy.log(
1865 "WARNING: skipping environ '{}' on exception '{}'".format(k, e)
1866 )
tiernoc94c3df2018-02-09 15:38:54 +01001867
1868 if update_dict:
1869 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +02001870 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001871
1872 # logging cherrypy
garciadeblas4568a372021-03-24 09:19:48 +01001873 log_format_simple = (
1874 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1875 )
1876 log_formatter_simple = logging.Formatter(
1877 log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S"
1878 )
tiernoc94c3df2018-02-09 15:38:54 +01001879 logger_server = logging.getLogger("cherrypy.error")
1880 logger_access = logging.getLogger("cherrypy.access")
1881 logger_cherry = logging.getLogger("cherrypy")
1882 logger_nbi = logging.getLogger("nbi")
1883
tiernof5298be2018-05-16 14:43:57 +02001884 if "log.file" in engine_config["global"]:
garciadeblas4568a372021-03-24 09:19:48 +01001885 file_handler = logging.handlers.RotatingFileHandler(
1886 engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0
1887 )
tiernoc94c3df2018-02-09 15:38:54 +01001888 file_handler.setFormatter(log_formatter_simple)
1889 logger_cherry.addHandler(file_handler)
1890 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +02001891 # log always to standard output
garciadeblas4568a372021-03-24 09:19:48 +01001892 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():
tiernob24258a2018-10-04 18:39:49 +02001897 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
garciadeblas4568a372021-03-24 09:19:48 +01001898 log_formatter_cherry = logging.Formatter(
1899 log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S"
1900 )
tiernob24258a2018-10-04 18:39:49 +02001901 str_handler = logging.StreamHandler()
1902 str_handler.setFormatter(log_formatter_cherry)
1903 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +01001904
tiernof5298be2018-05-16 14:43:57 +02001905 if engine_config["global"].get("log.level"):
1906 logger_cherry.setLevel(engine_config["global"]["log.level"])
1907 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +01001908
1909 # logging other modules
garciadeblas4568a372021-03-24 09:19:48 +01001910 for k1, logname in {
1911 "message": "nbi.msg",
1912 "database": "nbi.db",
1913 "storage": "nbi.fs",
1914 }.items():
tiernoc94c3df2018-02-09 15:38:54 +01001915 engine_config[k1]["logger_name"] = logname
1916 logger_module = logging.getLogger(logname)
1917 if "logfile" in engine_config[k1]:
garciadeblas4568a372021-03-24 09:19:48 +01001918 file_handler = logging.handlers.RotatingFileHandler(
1919 engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0
1920 )
tiernoc94c3df2018-02-09 15:38:54 +01001921 file_handler.setFormatter(log_formatter_simple)
1922 logger_module.addHandler(file_handler)
1923 if "loglevel" in engine_config[k1]:
1924 logger_module.setLevel(engine_config[k1]["loglevel"])
1925 # TODO add more entries, e.g.: storage
garciadeblas4568a372021-03-24 09:19:48 +01001926 cherrypy.tree.apps["/osm"].root.engine.start(engine_config)
1927 cherrypy.tree.apps["/osm"].root.authenticator.start(engine_config)
1928 cherrypy.tree.apps["/osm"].root.engine.init_db(target_version=database_version)
1929 cherrypy.tree.apps["/osm"].root.authenticator.init_db(
1930 target_version=auth_database_version
1931 )
tiernobee508e2019-01-21 11:21:49 +00001932
tierno932499c2019-01-28 17:28:10 +00001933 # start subscriptions thread:
garciadeblas4568a372021-03-24 09:19:48 +01001934 subscription_thread = SubscriptionThread(
1935 config=engine_config, engine=nbi_server.engine
1936 )
tierno932499c2019-01-28 17:28:10 +00001937 subscription_thread.start()
1938 # Do not capture except SubscriptionException
1939
Patricia Reinoso62fa6732023-02-22 17:57:53 +00001940 WFTemporal.temporal_api = (
1941 f'{engine_config["temporal"]["host"]}:{engine_config["temporal"]["port"]}'
1942 )
1943
tiernob2e48bd2020-02-04 15:47:18 +00001944 backend = engine_config["authentication"]["backend"]
garciadeblas4568a372021-03-24 09:19:48 +01001945 cherrypy.log.error(
1946 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1947 nbi_version, nbi_version_date, backend
1948 )
1949 )
tiernoc94c3df2018-02-09 15:38:54 +01001950
1951
1952def _stop_service():
1953 """
1954 Callback function called when cherrypy.engine stops
1955 TODO: Ending database connections.
1956 """
tierno932499c2019-01-28 17:28:10 +00001957 global subscription_thread
tierno65ca36d2019-02-12 19:27:52 +01001958 if subscription_thread:
1959 subscription_thread.terminate()
tierno932499c2019-01-28 17:28:10 +00001960 subscription_thread = None
garciadeblas4568a372021-03-24 09:19:48 +01001961 cherrypy.tree.apps["/osm"].root.engine.stop()
tiernoc94c3df2018-02-09 15:38:54 +01001962 cherrypy.log.error("Stopping osm_nbi")
1963
tierno2236d202018-05-16 19:05:16 +02001964
tiernof5298be2018-05-16 14:43:57 +02001965def nbi(config_file):
tierno932499c2019-01-28 17:28:10 +00001966 global nbi_server
tiernoc94c3df2018-02-09 15:38:54 +01001967 # 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})
tierno932499c2019-01-28 17:28:10 +00001984 nbi_server = Server()
garciadeblas4568a372021-03-24 09:19:48 +01001985 cherrypy.engine.subscribe("start", _start_service)
1986 cherrypy.engine.subscribe("stop", _stop_service)
1987 cherrypy.quickstart(nbi_server, "/osm", config_file)
tiernof5298be2018-05-16 14:43:57 +02001988
1989
1990def usage():
garciadeblas4568a372021-03-24 09:19:48 +01001991 print(
1992 """Usage: {} [options]
tiernof5298be2018-05-16 14:43:57 +02001993 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1994 -h|--help: shows this help
garciadeblas4568a372021-03-24 09:19:48 +01001995 """.format(
1996 sys.argv[0]
1997 )
1998 )
tierno2236d202018-05-16 19:05:16 +02001999 # --log-socket-host HOST: send logs to this host")
2000 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +01002001
2002
garciadeblas4568a372021-03-24 09:19:48 +01002003if __name__ == "__main__":
tiernof5298be2018-05-16 14:43:57 +02002004 try:
2005 # load parameters and configuration
2006 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
2007 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
2008 config_file = None
2009 for o, a in opts:
2010 if o in ("-h", "--help"):
2011 usage()
2012 sys.exit()
2013 elif o in ("-c", "--config"):
2014 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 assert False, "Unhandled option"
2023 if config_file:
2024 if not path.isfile(config_file):
garciadeblas4568a372021-03-24 09:19:48 +01002025 print(
2026 "configuration file '{}' that not exist".format(config_file),
2027 file=sys.stderr,
2028 )
tiernof5298be2018-05-16 14:43:57 +02002029 exit(1)
2030 else:
garciadeblas4568a372021-03-24 09:19:48 +01002031 for config_file in (
2032 __file__[: __file__.rfind(".")] + ".cfg",
2033 "./nbi.cfg",
2034 "/etc/osm/nbi.cfg",
2035 ):
tiernof5298be2018-05-16 14:43:57 +02002036 if path.isfile(config_file):
2037 break
2038 else:
garciadeblas4568a372021-03-24 09:19:48 +01002039 print(
2040 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
2041 file=sys.stderr,
2042 )
tiernof5298be2018-05-16 14:43:57 +02002043 exit(1)
2044 nbi(config_file)
2045 except getopt.GetoptError as e:
2046 print(str(e), file=sys.stderr)
2047 # usage()
2048 exit(1)