blob: dcee50cc78aca7bfe08ed053dbd1636f43cf43d9 [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
elumalai7802ff82023-04-24 20:38:32 +053031from osm_nbi.utils import cef_event, cef_event_builder
tierno9c630112019-08-29 14:21:41 +000032from osm_nbi.validation import ValidationError
tiernoa8d63632018-05-10 13:12:32 +020033from osm_common.dbbase import DbException
34from osm_common.fsbase import FsException
35from osm_common.msgbase import MsgException
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
elumalai7802ff82023-04-24 20:38:32 +053050cef_logger = None
tiernoc94c3df2018-02-09 15:38:54 +010051
52"""
tiernof27c79b2018-03-12 17:08:42 +010053North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
tiernoc94c3df2018-02-09 15:38:54 +010054URL: /osm GET POST PUT DELETE PATCH
garciadeblas9750c5a2018-10-15 16:20:35 +020055 /nsd/v1
tierno2236d202018-05-16 19:05:16 +020056 /ns_descriptors_content O O
57 /<nsdInfoId> O O O O
tiernoc94c3df2018-02-09 15:38:54 +010058 /ns_descriptors O5 O5
59 /<nsdInfoId> O5 O5 5
60 /nsd_content O5 O5
tiernof27c79b2018-03-12 17:08:42 +010061 /nsd O
62 /artifacts[/<artifactPath>] O
kayal2001f71c2e82024-06-25 15:26:24 +053063 /ns_config_template O O
64 /<nsConfigTemplateId> O O
65 /template_content O O
tiernoc94c3df2018-02-09 15:38:54 +010066 /pnf_descriptors 5 5
67 /<pnfdInfoId> 5 5 5
68 /pnfd_content 5 5
tiernof27c79b2018-03-12 17:08:42 +010069 /subscriptions 5 5
70 /<subscriptionId> 5 X
tiernoc94c3df2018-02-09 15:38:54 +010071
72 /vnfpkgm/v1
tierno55945e72018-04-06 16:40:27 +020073 /vnf_packages_content O O
tierno2236d202018-05-16 19:05:16 +020074 /<vnfPkgId> O O
tiernoc94c3df2018-02-09 15:38:54 +010075 /vnf_packages O5 O5
76 /<vnfPkgId> O5 O5 5
tiernoc94c3df2018-02-09 15:38:54 +010077 /package_content O5 O5
78 /upload_from_uri X
tiernof27c79b2018-03-12 17:08:42 +010079 /vnfd O5
80 /artifacts[/<artifactPath>] O5
81 /subscriptions X X
82 /<subscriptionId> X X
tiernoc94c3df2018-02-09 15:38:54 +010083
84 /nslcm/v1
tiernof27c79b2018-03-12 17:08:42 +010085 /ns_instances_content O O
tierno2236d202018-05-16 19:05:16 +020086 /<nsInstanceId> O O
tiernof27c79b2018-03-12 17:08:42 +010087 /ns_instances 5 5
tierno95692442018-05-24 18:05:28 +020088 /<nsInstanceId> O5 O5
tierno65acb4d2018-04-06 16:42:40 +020089 instantiate O5
90 terminate O5
91 action O
92 scale O5
elumalai8e3806c2022-04-28 17:26:24 +053093 migrate O
aticig544a2ae2022-04-05 09:00:17 +030094 update 05
garciadeblas0964edf2022-02-11 00:43:44 +010095 heal O5
tiernoc94c3df2018-02-09 15:38:54 +010096 /ns_lcm_op_occs 5 5
97 /<nsLcmOpOccId> 5 5 5
Gabriel Cuba84a60df2023-10-30 14:01:54 -050098 cancel 05
tiernof759d822018-06-11 18:54:54 +020099 /vnf_instances (also vnfrs for compatibility) O
100 /<vnfInstanceId> O
tiernof27c79b2018-03-12 17:08:42 +0100101 /subscriptions 5 5
102 /<subscriptionId> 5 X
garciadeblas9750c5a2018-10-15 16:20:35 +0200103
tiernocb83c942018-09-24 17:28:13 +0200104 /pdu/v1
tierno032916c2019-03-22 13:27:12 +0000105 /pdu_descriptors O O
tiernocb83c942018-09-24 17:28:13 +0200106 /<id> O O O O
garciadeblas9750c5a2018-10-15 16:20:35 +0200107
tiernof27c79b2018-03-12 17:08:42 +0100108 /admin/v1
109 /tokens O O
tierno2236d202018-05-16 19:05:16 +0200110 /<id> O O
tiernof27c79b2018-03-12 17:08:42 +0100111 /users O O
tiernocd54a4a2018-09-12 16:40:35 +0200112 /<id> O O O O
tiernof27c79b2018-03-12 17:08:42 +0100113 /projects O O
tierno2236d202018-05-16 19:05:16 +0200114 /<id> O O
tierno55ba2e62018-12-11 17:22:22 +0000115 /vim_accounts (also vims for compatibility) O O
116 /<id> O O O
117 /wim_accounts O O
tierno2236d202018-05-16 19:05:16 +0200118 /<id> O O O
tierno0f98af52018-03-19 10:28:22 +0100119 /sdns O O
tierno2236d202018-05-16 19:05:16 +0200120 /<id> O O O
delacruzramofe598fe2019-10-23 18:25:11 +0200121 /k8sclusters O O
122 /<id> O O O
123 /k8srepos O O
124 /<id> O O
Felipe Vicensb66b0412020-05-06 10:11:00 +0200125 /osmrepos O O
126 /<id> O O
tiernoc94c3df2018-02-09 15:38:54 +0100127
garciadeblas9750c5a2018-10-15 16:20:35 +0200128 /nst/v1 O O
129 /netslice_templates_content O O
130 /<nstInfoId> O O O O
131 /netslice_templates O O
132 /<nstInfoId> O O O
133 /nst_content O O
134 /nst O
135 /artifacts[/<artifactPath>] O
136 /subscriptions X X
137 /<subscriptionId> X X
138
139 /nsilcm/v1
140 /netslice_instances_content O O
141 /<SliceInstanceId> O O
142 /netslice_instances O O
143 /<SliceInstanceId> O O
144 instantiate O
145 terminate O
146 action O
147 /nsi_lcm_op_occs O O
148 /<nsiLcmOpOccId> O O O
149 /subscriptions X X
150 /<subscriptionId> X X
151
tierno2236d202018-05-16 19:05:16 +0200152query string:
153 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
tierno36ec8602018-11-02 17:27:11 +0100154 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
155 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
156 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
157 attrName := string
tierno2236d202018-05-16 19:05:16 +0200158 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
159 item of the array, that is, pass if any item of the array pass the filter.
160 It allows both ne and neq for not equal
161 TODO: 4.3.3 Attribute selectors
162 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
tiernoc94c3df2018-02-09 15:38:54 +0100163 (none) … same as “exclude_default”
164 all_fields … all attributes.
tierno2236d202018-05-16 19:05:16 +0200165 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
166 conditionally mandatory, and that are not provided in <list>.
167 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
168 are not conditionally mandatory, and that are provided in <list>.
169 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
170 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
171 the particular resource
172 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
173 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
174 present specification for the particular resource, but that are not part of <list>
tierno65ca36d2019-02-12 19:27:52 +0100175 Additionally it admits some administrator values:
176 FORCE: To force operations skipping dependency checkings
177 ADMIN: To act as an administrator or a different project
178 PUBLIC: To get public descriptors or set a descriptor as public
179 SET_PROJECT: To make a descriptor available for other project
beierlmbc5a5242022-05-17 21:25:29 -0400180
tiernoc94c3df2018-02-09 15:38:54 +0100181Header field name Reference Example Descriptions
182 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
183 This header field shall be present if the response is expected to have a non-empty message body.
184 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
185 This header field shall be present if the request has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200186 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
187 Details are specified in clause 4.5.3.
tiernoc94c3df2018-02-09 15:38:54 +0100188 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
189Header field name Reference Example Descriptions
190 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
191 This header field shall be present if the response has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200192 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
193 new resource has been created.
tiernoc94c3df2018-02-09 15:38:54 +0100194 This header field shall be present if the response status code is 201 or 3xx.
tierno2236d202018-05-16 19:05:16 +0200195 In the present document this header field is also used if the response status code is 202 and a new resource was
196 created.
197 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
198 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
199 token.
200 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
201 certain resources.
202 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
203 response, and the total length of the file.
tiernoc94c3df2018-02-09 15:38:54 +0100204 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
tiernoc94c3df2018-02-09 15:38:54 +0100205"""
206
tierno701018c2019-06-25 11:13:14 +0000207valid_query_string = ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
208# ^ Contains possible administrative query string words:
209# ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
210# (not owned by my session project).
211# PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
212# FORCE=True(by default)|False: Force edition/deletion operations
213# SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
214
215valid_url_methods = {
216 # contains allowed URL and methods, and the role_permission name
217 "admin": {
218 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100219 "tokens": {
220 "METHODS": ("GET", "POST", "DELETE"),
221 "ROLE_PERMISSION": "tokens:",
222 "<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"},
223 },
224 "users": {
225 "METHODS": ("GET", "POST"),
226 "ROLE_PERMISSION": "users:",
227 "<ID>": {
228 "METHODS": ("GET", "DELETE", "PATCH"),
229 "ROLE_PERMISSION": "users:id:",
230 },
231 },
232 "projects": {
233 "METHODS": ("GET", "POST"),
234 "ROLE_PERMISSION": "projects:",
235 "<ID>": {
236 "METHODS": ("GET", "DELETE", "PATCH"),
237 "ROLE_PERMISSION": "projects:id:",
238 },
239 },
240 "roles": {
241 "METHODS": ("GET", "POST"),
242 "ROLE_PERMISSION": "roles:",
243 "<ID>": {
244 "METHODS": ("GET", "DELETE", "PATCH"),
245 "ROLE_PERMISSION": "roles:id:",
246 },
247 },
248 "vims": {
249 "METHODS": ("GET", "POST"),
250 "ROLE_PERMISSION": "vims:",
251 "<ID>": {
252 "METHODS": ("GET", "DELETE", "PATCH"),
253 "ROLE_PERMISSION": "vims:id:",
254 },
255 },
256 "vim_accounts": {
257 "METHODS": ("GET", "POST"),
258 "ROLE_PERMISSION": "vim_accounts:",
259 "<ID>": {
260 "METHODS": ("GET", "DELETE", "PATCH"),
261 "ROLE_PERMISSION": "vim_accounts:id:",
262 },
263 },
264 "wim_accounts": {
265 "METHODS": ("GET", "POST"),
266 "ROLE_PERMISSION": "wim_accounts:",
267 "<ID>": {
268 "METHODS": ("GET", "DELETE", "PATCH"),
269 "ROLE_PERMISSION": "wim_accounts:id:",
270 },
271 },
272 "sdns": {
273 "METHODS": ("GET", "POST"),
274 "ROLE_PERMISSION": "sdn_controllers:",
275 "<ID>": {
276 "METHODS": ("GET", "DELETE", "PATCH"),
277 "ROLE_PERMISSION": "sdn_controllers:id:",
278 },
279 },
280 "k8sclusters": {
281 "METHODS": ("GET", "POST"),
282 "ROLE_PERMISSION": "k8sclusters:",
283 "<ID>": {
284 "METHODS": ("GET", "DELETE", "PATCH"),
285 "ROLE_PERMISSION": "k8sclusters:id:",
286 },
287 },
288 "vca": {
289 "METHODS": ("GET", "POST"),
290 "ROLE_PERMISSION": "vca:",
291 "<ID>": {
292 "METHODS": ("GET", "DELETE", "PATCH"),
293 "ROLE_PERMISSION": "vca:id:",
294 },
295 },
296 "k8srepos": {
297 "METHODS": ("GET", "POST"),
298 "ROLE_PERMISSION": "k8srepos:",
299 "<ID>": {
300 "METHODS": ("GET", "DELETE"),
301 "ROLE_PERMISSION": "k8srepos:id:",
302 },
303 },
304 "osmrepos": {
305 "METHODS": ("GET", "POST"),
306 "ROLE_PERMISSION": "osmrepos:",
307 "<ID>": {
308 "METHODS": ("GET", "DELETE", "PATCH"),
309 "ROLE_PERMISSION": "osmrepos:id:",
310 },
311 },
312 "domains": {
313 "METHODS": ("GET",),
314 "ROLE_PERMISSION": "domains:",
315 },
tierno701018c2019-06-25 11:13:14 +0000316 }
317 },
318 "pdu": {
319 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100320 "pdu_descriptors": {
321 "METHODS": ("GET", "POST"),
322 "ROLE_PERMISSION": "pduds:",
323 "<ID>": {
324 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
325 "ROLE_PERMISSION": "pduds:id:",
326 },
327 },
tierno701018c2019-06-25 11:13:14 +0000328 }
329 },
330 "nsd": {
331 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100332 "ns_descriptors_content": {
333 "METHODS": ("GET", "POST"),
334 "ROLE_PERMISSION": "nsds:",
335 "<ID>": {
336 "METHODS": ("GET", "PUT", "DELETE"),
337 "ROLE_PERMISSION": "nsds:id:",
338 },
339 },
340 "ns_descriptors": {
341 "METHODS": ("GET", "POST"),
342 "ROLE_PERMISSION": "nsds:",
343 "<ID>": {
344 "METHODS": ("GET", "DELETE", "PATCH"),
345 "ROLE_PERMISSION": "nsds:id:",
346 "nsd_content": {
347 "METHODS": ("GET", "PUT"),
348 "ROLE_PERMISSION": "nsds:id:content:",
349 },
350 "nsd": {
351 "METHODS": ("GET",), # descriptor inside package
352 "ROLE_PERMISSION": "nsds:id:content:",
353 },
354 "artifacts": {
355 "METHODS": ("GET",),
356 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
357 "*": None,
358 },
359 },
360 },
kayal2001f71c2e82024-06-25 15:26:24 +0530361 "ns_config_template": {
362 "METHODS": ("GET", "POST"),
363 "ROLE_PERMISSION": "ns_config_template:content:",
364 "<ID>": {
365 "METHODS": ("GET", "DELETE"),
366 "ROLE_PERMISSION": "ns_config_template:id:",
367 "template_content": {
368 "METHODS": ("GET", "PUT"),
369 "ROLE_PERMISSION": "ns_config_template:id:content:",
370 },
371 },
372 },
garciadeblas4568a372021-03-24 09:19:48 +0100373 "pnf_descriptors": {
374 "TODO": ("GET", "POST"),
375 "<ID>": {
376 "TODO": ("GET", "DELETE", "PATCH"),
377 "pnfd_content": {"TODO": ("GET", "PUT")},
378 },
379 },
380 "subscriptions": {
381 "TODO": ("GET", "POST"),
382 "<ID>": {"TODO": ("GET", "DELETE")},
383 },
tierno701018c2019-06-25 11:13:14 +0000384 }
385 },
386 "vnfpkgm": {
387 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100388 "vnf_packages_content": {
389 "METHODS": ("GET", "POST"),
390 "ROLE_PERMISSION": "vnfds:",
391 "<ID>": {
392 "METHODS": ("GET", "PUT", "DELETE"),
393 "ROLE_PERMISSION": "vnfds:id:",
394 },
395 },
396 "vnf_packages": {
397 "METHODS": ("GET", "POST"),
398 "ROLE_PERMISSION": "vnfds:",
399 "<ID>": {
400 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
401 "ROLE_PERMISSION": "vnfds:id:",
402 "package_content": {
403 "METHODS": ("GET", "PUT"), # package
404 "ROLE_PERMISSION": "vnfds:id:",
405 "upload_from_uri": {
406 "METHODS": (),
407 "TODO": ("POST",),
408 "ROLE_PERMISSION": "vnfds:id:upload:",
409 },
410 },
411 "vnfd": {
412 "METHODS": ("GET",), # descriptor inside package
413 "ROLE_PERMISSION": "vnfds:id:content:",
414 },
415 "artifacts": {
416 "METHODS": ("GET",),
417 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
418 "*": None,
419 },
420 "action": {
421 "METHODS": ("POST",),
422 "ROLE_PERMISSION": "vnfds:id:action:",
423 },
424 },
425 },
426 "subscriptions": {
427 "TODO": ("GET", "POST"),
428 "<ID>": {"TODO": ("GET", "DELETE")},
429 },
430 "vnfpkg_op_occs": {
431 "METHODS": ("GET",),
432 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
433 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"},
434 },
tierno701018c2019-06-25 11:13:14 +0000435 }
436 },
437 "nslcm": {
438 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100439 "ns_instances_content": {
440 "METHODS": ("GET", "POST"),
441 "ROLE_PERMISSION": "ns_instances:",
442 "<ID>": {
443 "METHODS": ("GET", "DELETE"),
444 "ROLE_PERMISSION": "ns_instances:id:",
445 },
446 },
447 "ns_instances": {
448 "METHODS": ("GET", "POST"),
449 "ROLE_PERMISSION": "ns_instances:",
450 "<ID>": {
451 "METHODS": ("GET", "DELETE"),
452 "ROLE_PERMISSION": "ns_instances:id:",
garciadeblas0964edf2022-02-11 00:43:44 +0100453 "heal": {
454 "METHODS": ("POST",),
455 "ROLE_PERMISSION": "ns_instances:id:heal:",
456 },
garciadeblas4568a372021-03-24 09:19:48 +0100457 "scale": {
458 "METHODS": ("POST",),
459 "ROLE_PERMISSION": "ns_instances:id:scale:",
460 },
461 "terminate": {
462 "METHODS": ("POST",),
463 "ROLE_PERMISSION": "ns_instances:id:terminate:",
464 },
465 "instantiate": {
466 "METHODS": ("POST",),
467 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
468 },
elumalai8e3806c2022-04-28 17:26:24 +0530469 "migrate": {
470 "METHODS": ("POST",),
471 "ROLE_PERMISSION": "ns_instances:id:migrate:",
472 },
garciadeblas4568a372021-03-24 09:19:48 +0100473 "action": {
474 "METHODS": ("POST",),
475 "ROLE_PERMISSION": "ns_instances:id:action:",
476 },
aticig544a2ae2022-04-05 09:00:17 +0300477 "update": {
478 "METHODS": ("POST",),
479 "ROLE_PERMISSION": "ns_instances:id:update:",
480 },
garciadeblas4568a372021-03-24 09:19:48 +0100481 },
482 },
483 "ns_lcm_op_occs": {
484 "METHODS": ("GET",),
485 "ROLE_PERMISSION": "ns_instances:opps:",
486 "<ID>": {
487 "METHODS": ("GET",),
488 "ROLE_PERMISSION": "ns_instances:opps:id:",
Gabriel Cuba84a60df2023-10-30 14:01:54 -0500489 "cancel": {
490 "METHODS": ("POST",),
491 "ROLE_PERMISSION": "ns_instances:opps:cancel:",
492 },
garciadeblas4568a372021-03-24 09:19:48 +0100493 },
494 },
495 "vnfrs": {
496 "METHODS": ("GET",),
497 "ROLE_PERMISSION": "vnf_instances:",
498 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
499 },
500 "vnf_instances": {
501 "METHODS": ("GET",),
502 "ROLE_PERMISSION": "vnf_instances:",
503 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
504 },
505 "subscriptions": {
506 "METHODS": ("GET", "POST"),
507 "ROLE_PERMISSION": "ns_subscriptions:",
508 "<ID>": {
509 "METHODS": ("GET", "DELETE"),
510 "ROLE_PERMISSION": "ns_subscriptions:id:",
511 },
512 },
tierno701018c2019-06-25 11:13:14 +0000513 }
514 },
almagiae47b9132022-05-17 14:12:22 +0200515 "vnflcm": {
516 "v1": {
garciadeblasf2af4a12023-01-24 16:56:54 +0100517 "vnf_instances": {
518 "METHODS": ("GET", "POST"),
519 "ROLE_PERMISSION": "vnflcm_instances:",
520 "<ID>": {
521 "METHODS": ("GET", "DELETE"),
522 "ROLE_PERMISSION": "vnflcm_instances:id:",
523 "scale": {
524 "METHODS": ("POST",),
525 "ROLE_PERMISSION": "vnflcm_instances:id:scale:",
526 },
527 "terminate": {
528 "METHODS": ("POST",),
529 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:",
530 },
531 "instantiate": {
532 "METHODS": ("POST",),
533 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:",
534 },
535 },
536 },
537 "vnf_lcm_op_occs": {
538 "METHODS": ("GET",),
539 "ROLE_PERMISSION": "vnf_instances:opps:",
540 "<ID>": {
541 "METHODS": ("GET",),
542 "ROLE_PERMISSION": "vnf_instances:opps:id:",
543 },
544 },
545 "subscriptions": {
546 "METHODS": ("GET", "POST"),
547 "ROLE_PERMISSION": "vnflcm_subscriptions:",
548 "<ID>": {
549 "METHODS": ("GET", "DELETE"),
550 "ROLE_PERMISSION": "vnflcm_subscriptions:id:",
551 },
552 },
almagiae47b9132022-05-17 14:12:22 +0200553 }
554 },
tierno701018c2019-06-25 11:13:14 +0000555 "nst": {
556 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100557 "netslice_templates_content": {
558 "METHODS": ("GET", "POST"),
559 "ROLE_PERMISSION": "slice_templates:",
560 "<ID>": {
561 "METHODS": ("GET", "PUT", "DELETE"),
562 "ROLE_PERMISSION": "slice_templates:id:",
563 },
564 },
565 "netslice_templates": {
566 "METHODS": ("GET", "POST"),
567 "ROLE_PERMISSION": "slice_templates:",
568 "<ID>": {
569 "METHODS": ("GET", "DELETE"),
570 "TODO": ("PATCH",),
571 "ROLE_PERMISSION": "slice_templates:id:",
572 "nst_content": {
573 "METHODS": ("GET", "PUT"),
574 "ROLE_PERMISSION": "slice_templates:id:content:",
575 },
576 "nst": {
577 "METHODS": ("GET",), # descriptor inside package
578 "ROLE_PERMISSION": "slice_templates:id:content:",
579 },
580 "artifacts": {
581 "METHODS": ("GET",),
582 "ROLE_PERMISSION": "slice_templates:id:content:",
583 "*": None,
584 },
585 },
586 },
587 "subscriptions": {
588 "TODO": ("GET", "POST"),
589 "<ID>": {"TODO": ("GET", "DELETE")},
590 },
tierno701018c2019-06-25 11:13:14 +0000591 }
592 },
593 "nsilcm": {
594 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100595 "netslice_instances_content": {
596 "METHODS": ("GET", "POST"),
597 "ROLE_PERMISSION": "slice_instances:",
598 "<ID>": {
599 "METHODS": ("GET", "DELETE"),
600 "ROLE_PERMISSION": "slice_instances:id:",
601 },
602 },
603 "netslice_instances": {
604 "METHODS": ("GET", "POST"),
605 "ROLE_PERMISSION": "slice_instances:",
606 "<ID>": {
607 "METHODS": ("GET", "DELETE"),
608 "ROLE_PERMISSION": "slice_instances:id:",
609 "terminate": {
610 "METHODS": ("POST",),
611 "ROLE_PERMISSION": "slice_instances:id:terminate:",
612 },
613 "instantiate": {
614 "METHODS": ("POST",),
615 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
616 },
617 "action": {
618 "METHODS": ("POST",),
619 "ROLE_PERMISSION": "slice_instances:id:action:",
620 },
621 },
622 },
623 "nsi_lcm_op_occs": {
624 "METHODS": ("GET",),
625 "ROLE_PERMISSION": "slice_instances:opps:",
626 "<ID>": {
627 "METHODS": ("GET",),
628 "ROLE_PERMISSION": "slice_instances:opps:id:",
629 },
630 },
tierno701018c2019-06-25 11:13:14 +0000631 }
632 },
633 "nspm": {
634 "v1": {
635 "pm_jobs": {
636 "<ID>": {
637 "reports": {
garciadeblas4568a372021-03-24 09:19:48 +0100638 "<ID>": {
639 "METHODS": ("GET",),
640 "ROLE_PERMISSION": "reports:id:",
641 }
tierno701018c2019-06-25 11:13:14 +0000642 }
643 },
644 },
645 },
646 },
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000647 "nsfm": {
648 "v1": {
garciadeblasf2af4a12023-01-24 16:56:54 +0100649 "alarms": {
650 "METHODS": ("GET", "PATCH"),
651 "ROLE_PERMISSION": "alarms:",
652 "<ID>": {
653 "METHODS": ("GET", "PATCH"),
654 "ROLE_PERMISSION": "alarms:id:",
655 },
656 }
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000657 },
658 },
tierno701018c2019-06-25 11:13:14 +0000659}
660
tiernoc94c3df2018-02-09 15:38:54 +0100661
662class NbiException(Exception):
tiernoc94c3df2018-02-09 15:38:54 +0100663 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
664 Exception.__init__(self, message)
665 self.http_code = http_code
666
667
668class Server(object):
669 instance = 0
670 # to decode bytes to str
671 reader = getreader("utf-8")
672
673 def __init__(self):
674 self.instance += 1
tierno701018c2019-06-25 11:13:14 +0000675 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
delacruzramoad682a52019-12-10 16:26:34 +0100676 self.engine = Engine(self.authenticator)
tiernoc94c3df2018-02-09 15:38:54 +0100677
tiernoc94c3df2018-02-09 15:38:54 +0100678 def _format_in(self, kwargs):
garciadeblasf2af4a12023-01-24 16:56:54 +0100679 error_text = "" # error_text must be initialized outside try
tiernoc94c3df2018-02-09 15:38:54 +0100680 try:
681 indata = None
682 if cherrypy.request.body.length:
683 error_text = "Invalid input format "
684
685 if "Content-Type" in cherrypy.request.headers:
686 if "application/json" in cherrypy.request.headers["Content-Type"]:
687 error_text = "Invalid json format "
688 indata = json.load(self.reader(cherrypy.request.body))
gcalvinode4adfe2018-10-30 11:46:09 +0100689 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100690 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
691 error_text = "Invalid yaml format "
garciadeblas4cd875d2023-02-14 19:05:34 +0100692 indata = yaml.safe_load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100693 cherrypy.request.headers.pop("Content-File-MD5", None)
garciadeblas4568a372021-03-24 09:19:48 +0100694 elif (
695 "application/binary" in cherrypy.request.headers["Content-Type"]
696 or "application/gzip"
697 in cherrypy.request.headers["Content-Type"]
698 or "application/zip" in cherrypy.request.headers["Content-Type"]
699 or "text/plain" in cherrypy.request.headers["Content-Type"]
700 ):
tiernof27c79b2018-03-12 17:08:42 +0100701 indata = cherrypy.request.body # .read()
garciadeblas4568a372021-03-24 09:19:48 +0100702 elif (
703 "multipart/form-data"
704 in cherrypy.request.headers["Content-Type"]
705 ):
tiernoc94c3df2018-02-09 15:38:54 +0100706 if "descriptor_file" in kwargs:
707 filecontent = kwargs.pop("descriptor_file")
708 if not filecontent.file:
garciadeblas4568a372021-03-24 09:19:48 +0100709 raise NbiException(
710 "empty file or content", HTTPStatus.BAD_REQUEST
711 )
tiernof27c79b2018-03-12 17:08:42 +0100712 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100713 if filecontent.content_type.value:
garciadeblas4568a372021-03-24 09:19:48 +0100714 cherrypy.request.headers[
715 "Content-Type"
716 ] = filecontent.content_type.value
tiernoc94c3df2018-02-09 15:38:54 +0100717 else:
718 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
719 # "Only 'Content-Type' of type 'application/json' or
720 # 'application/yaml' for input format are available")
721 error_text = "Invalid yaml format "
garciadeblas4cd875d2023-02-14 19:05:34 +0100722 indata = yaml.safe_load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100723 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100724 else:
725 error_text = "Invalid yaml format "
garciadeblas4cd875d2023-02-14 19:05:34 +0100726 indata = yaml.safe_load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100727 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100728 if not indata:
729 indata = {}
730
tiernoc94c3df2018-02-09 15:38:54 +0100731 format_yaml = False
732 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
733 format_yaml = True
734
735 for k, v in kwargs.items():
736 if isinstance(v, str):
737 if v == "":
738 kwargs[k] = None
739 elif format_yaml:
740 try:
garciadeblas4cd875d2023-02-14 19:05:34 +0100741 kwargs[k] = yaml.safe_load(v)
tiernoe1281182018-05-22 12:24:36 +0200742 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100743 pass
garciadeblas4568a372021-03-24 09:19:48 +0100744 elif (
745 k.endswith(".gt")
746 or k.endswith(".lt")
747 or k.endswith(".gte")
748 or k.endswith(".lte")
749 ):
tiernoc94c3df2018-02-09 15:38:54 +0100750 try:
751 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200752 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100753 try:
754 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200755 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100756 pass
757 elif v.find(",") > 0:
758 kwargs[k] = v.split(",")
759 elif isinstance(v, (list, tuple)):
760 for index in range(0, len(v)):
761 if v[index] == "":
762 v[index] = None
763 elif format_yaml:
764 try:
garciadeblas4cd875d2023-02-14 19:05:34 +0100765 v[index] = yaml.safe_load(v[index])
tiernoe1281182018-05-22 12:24:36 +0200766 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100767 pass
768
tiernof27c79b2018-03-12 17:08:42 +0100769 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100770 except (ValueError, yaml.YAMLError) as exc:
771 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
772 except KeyError as exc:
garciadeblas4568a372021-03-24 09:19:48 +0100773 raise NbiException(
774 "Query string error: " + str(exc), HTTPStatus.BAD_REQUEST
775 )
tiernob92094f2018-05-11 13:44:22 +0200776 except Exception as exc:
777 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100778
779 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000780 def _format_out(data, token_info=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100781 """
782 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100783 :param data: response to be sent. Can be a dict, text or file
tierno701018c2019-06-25 11:13:14 +0000784 :param token_info: Contains among other username and project
tierno5792d7d2019-08-30 15:37:12 +0000785 :param _format: The format to be set as Content-Type if data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100786 :return: None
787 """
tierno0f98af52018-03-19 10:28:22 +0100788 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100789 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100790 if accept and "text/html" in accept:
garciadeblas4568a372021-03-24 09:19:48 +0100791 return html.format(
792 data, cherrypy.request, cherrypy.response, token_info
793 )
tierno09c073e2018-04-26 13:36:48 +0200794 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100795 return
796 elif hasattr(data, "read"): # file object
797 if _format:
798 cherrypy.response.headers["Content-Type"] = _format
799 elif "b" in data.mode: # binariy asssumig zip
garciadeblas4568a372021-03-24 09:19:48 +0100800 cherrypy.response.headers["Content-Type"] = "application/zip"
tiernof27c79b2018-03-12 17:08:42 +0100801 else:
garciadeblas4568a372021-03-24 09:19:48 +0100802 cherrypy.response.headers["Content-Type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100803 # TODO check that cherrypy close file. If not implement pending things to close per thread next
804 return data
tierno0f98af52018-03-19 10:28:22 +0100805 if accept:
Frank Bryden02e700c2020-06-03 13:34:16 +0000806 if "text/html" in accept:
garciadeblas4568a372021-03-24 09:19:48 +0100807 return html.format(
808 data, cherrypy.request, cherrypy.response, token_info
809 )
Frank Brydenb5422da2020-08-10 11:44:11 +0000810 elif "application/yaml" in accept or "*/*" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100811 pass
garciadeblas4568a372021-03-24 09:19:48 +0100812 elif "application/json" in accept or (
813 cherrypy.response.status and cherrypy.response.status >= 300
814 ):
815 cherrypy.response.headers[
816 "Content-Type"
817 ] = "application/json; charset=utf-8"
Frank Bryden02e700c2020-06-03 13:34:16 +0000818 a = json.dumps(data, indent=4) + "\n"
garciadeblas4568a372021-03-24 09:19:48 +0100819 return a.encode("utf8")
820 cherrypy.response.headers["Content-Type"] = "application/yaml"
821 return yaml.safe_dump(
822 data,
823 explicit_start=True,
824 indent=4,
825 default_flow_style=False,
826 tags=False,
827 encoding="utf-8",
828 allow_unicode=True,
829 ) # , canonical=True, default_style='"'
tiernoc94c3df2018-02-09 15:38:54 +0100830
831 @cherrypy.expose
832 def index(self, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000833 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100834 try:
835 if cherrypy.request.method == "GET":
tierno701018c2019-06-25 11:13:14 +0000836 token_info = self.authenticator.authorize()
garciadeblas4568a372021-03-24 09:19:48 +0100837 outdata = token_info # Home page
tiernoc94c3df2018-02-09 15:38:54 +0100838 else:
garciadeblas4568a372021-03-24 09:19:48 +0100839 raise cherrypy.HTTPError(
840 HTTPStatus.METHOD_NOT_ALLOWED.value,
841 "Method {} not allowed for tokens".format(cherrypy.request.method),
842 )
tiernoc94c3df2018-02-09 15:38:54 +0100843
tierno701018c2019-06-25 11:13:14 +0000844 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100845
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100846 except (EngineException, AuthException) as e:
tierno701018c2019-06-25 11:13:14 +0000847 # cherrypy.log("index Exception {}".format(e))
tiernoc94c3df2018-02-09 15:38:54 +0100848 cherrypy.response.status = e.http_code.value
tierno701018c2019-06-25 11:13:14 +0000849 return self._format_out("Welcome to OSM!", token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100850
851 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200852 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200853 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200854 try:
855 if cherrypy.request.method != "GET":
garciadeblas4568a372021-03-24 09:19:48 +0100856 raise NbiException(
857 "Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED
858 )
tierno55945e72018-04-06 16:40:27 +0200859 elif args or kwargs:
garciadeblas4568a372021-03-24 09:19:48 +0100860 raise NbiException(
861 "Invalid URL or query string for version",
862 HTTPStatus.METHOD_NOT_ALLOWED,
863 )
tierno9c630112019-08-29 14:21:41 +0000864 # TODO include version of other modules, pick up from some kafka admin message
tierno5792d7d2019-08-30 15:37:12 +0000865 osm_nbi_version = {"version": nbi_version, "date": nbi_version_date}
866 return self._format_out(osm_nbi_version)
tierno55945e72018-04-06 16:40:27 +0200867 except NbiException as e:
868 cherrypy.response.status = e.http_code.value
869 problem_details = {
870 "code": e.http_code.name,
871 "status": e.http_code.value,
872 "detail": str(e),
873 }
874 return self._format_out(problem_details, None)
875
tierno12eac3c2020-03-19 23:22:08 +0000876 def domain(self):
877 try:
878 domains = {
garciadeblas4568a372021-03-24 09:19:48 +0100879 "user_domain_name": cherrypy.tree.apps["/osm"]
880 .config["authentication"]
881 .get("user_domain_name"),
882 "project_domain_name": cherrypy.tree.apps["/osm"]
883 .config["authentication"]
884 .get("project_domain_name"),
885 }
tierno12eac3c2020-03-19 23:22:08 +0000886 return self._format_out(domains)
887 except NbiException as e:
888 cherrypy.response.status = e.http_code.value
889 problem_details = {
890 "code": e.http_code.name,
891 "status": e.http_code.value,
892 "detail": str(e),
893 }
894 return self._format_out(problem_details, None)
895
tiernoa5035702019-07-29 08:54:42 +0000896 @staticmethod
897 def _format_login(token_info):
898 """
899 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
900 log this information
901 :param token_info: Dictionary with token content
902 :return: None
903 """
904 cherrypy.request.login = token_info.get("username", "-")
905 if token_info.get("project_name"):
906 cherrypy.request.login += "/" + token_info["project_name"]
907 if token_info.get("id"):
908 cherrypy.request.login += ";session=" + token_info["id"][0:12]
909
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000910 # NS Fault Management
911 @cherrypy.expose
garciadeblasf2af4a12023-01-24 16:56:54 +0100912 def nsfm(
913 self,
914 version=None,
915 topic=None,
916 uuid=None,
917 project_name=None,
918 ns_id=None,
919 *args,
920 **kwargs
921 ):
922 if topic == "alarms":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000923 try:
924 method = cherrypy.request.method
garciadeblasf2af4a12023-01-24 16:56:54 +0100925 role_permission = self._check_valid_url_method(
926 method, "nsfm", version, topic, None, None, *args
927 )
928 query_string_operations = self._extract_query_string_operations(
929 kwargs, method
930 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000931
garciadeblasf2af4a12023-01-24 16:56:54 +0100932 self.authenticator.authorize(
933 role_permission, query_string_operations, None
934 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000935
936 # to handle get request
garciadeblasf2af4a12023-01-24 16:56:54 +0100937 if cherrypy.request.method == "GET":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000938 # if request is on basis of uuid
garciadeblasf2af4a12023-01-24 16:56:54 +0100939 if uuid and uuid != "None":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000940 try:
941 alarm = self.engine.db.get_one("alarms", {"uuid": uuid})
garciadeblasf2af4a12023-01-24 16:56:54 +0100942 alarm_action = self.engine.db.get_one(
943 "alarms_action", {"uuid": uuid}
944 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000945 alarm.update(alarm_action)
garciadeblasf2af4a12023-01-24 16:56:54 +0100946 vnf = self.engine.db.get_one(
947 "vnfrs", {"nsr-id-ref": alarm["tags"]["ns_id"]}
948 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000949 alarm["vnf-id"] = vnf["_id"]
950 return self._format_out(str(alarm))
951 except Exception:
952 return self._format_out("Please provide valid alarm uuid")
garciadeblasf2af4a12023-01-24 16:56:54 +0100953 elif ns_id and ns_id != "None":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000954 # if request is on basis of ns_id
955 try:
garciadeblasf2af4a12023-01-24 16:56:54 +0100956 alarms = self.engine.db.get_list(
957 "alarms", {"tags.ns_id": ns_id}
958 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000959 for alarm in alarms:
garciadeblasf2af4a12023-01-24 16:56:54 +0100960 alarm_action = self.engine.db.get_one(
961 "alarms_action", {"uuid": alarm["uuid"]}
962 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000963 alarm.update(alarm_action)
964 return self._format_out(str(alarms))
965 except Exception:
966 return self._format_out("Please provide valid ns id")
967 else:
968 # to return only alarm which are related to given project
garciadeblasf2af4a12023-01-24 16:56:54 +0100969 project = self.engine.db.get_one(
970 "projects", {"name": project_name}
971 )
972 project_id = project.get("_id")
973 ns_list = self.engine.db.get_list(
974 "nsrs", {"_admin.projects_read": project_id}
975 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000976 ns_ids = []
977 for ns in ns_list:
978 ns_ids.append(ns.get("_id"))
979 alarms = self.engine.db.get_list("alarms")
garciadeblasf2af4a12023-01-24 16:56:54 +0100980 alarm_list = [
981 alarm
982 for alarm in alarms
983 if alarm["tags"]["ns_id"] in ns_ids
984 ]
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000985 for alrm in alarm_list:
garciadeblasf2af4a12023-01-24 16:56:54 +0100986 action = self.engine.db.get_one(
987 "alarms_action", {"uuid": alrm.get("uuid")}
988 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000989 alrm.update(action)
990 return self._format_out(str(alarm_list))
991 # to handle patch request for alarm update
garciadeblasf2af4a12023-01-24 16:56:54 +0100992 elif cherrypy.request.method == "PATCH":
garciadeblas4cd875d2023-02-14 19:05:34 +0100993 data = yaml.safe_load(cherrypy.request.body)
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000994 try:
995 # check if uuid is valid
996 self.engine.db.get_one("alarms", {"uuid": data.get("uuid")})
997 except Exception:
998 return self._format_out("Please provide valid alarm uuid.")
999 if data.get("is_enable") is not None:
1000 if data.get("is_enable"):
garciadeblasf2af4a12023-01-24 16:56:54 +01001001 alarm_status = "ok"
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001002 else:
garciadeblasf2af4a12023-01-24 16:56:54 +01001003 alarm_status = "disabled"
1004 self.engine.db.set_one(
1005 "alarms",
1006 {"uuid": data.get("uuid")},
1007 {"alarm_status": alarm_status},
1008 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001009 else:
garciadeblasf2af4a12023-01-24 16:56:54 +01001010 self.engine.db.set_one(
1011 "alarms",
1012 {"uuid": data.get("uuid")},
1013 {"threshold": data.get("threshold")},
1014 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001015 return self._format_out("Alarm updated")
1016 except Exception as e:
garciadeblasf2af4a12023-01-24 16:56:54 +01001017 if isinstance(
1018 e,
1019 (
1020 NbiException,
1021 EngineException,
1022 DbException,
1023 FsException,
1024 MsgException,
1025 AuthException,
1026 ValidationError,
1027 AuthconnException,
1028 ),
1029 ):
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001030 http_code_value = cherrypy.response.status = e.http_code.value
1031 http_code_name = e.http_code.name
1032 cherrypy.log("Exception {}".format(e))
1033 else:
garciadeblasf2af4a12023-01-24 16:56:54 +01001034 http_code_value = (
1035 cherrypy.response.status
1036 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001037 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
1038 http_code_name = HTTPStatus.BAD_REQUEST.name
1039 problem_details = {
1040 "code": http_code_name,
1041 "status": http_code_value,
1042 "detail": str(e),
1043 }
1044 return self._format_out(problem_details)
1045
tierno55945e72018-04-06 16:40:27 +02001046 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +01001047 def token(self, method, token_id=None, kwargs=None):
tierno701018c2019-06-25 11:13:14 +00001048 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +01001049 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +01001050 indata = self._format_in(kwargs)
1051 if not isinstance(indata, dict):
garciadeblas4568a372021-03-24 09:19:48 +01001052 raise NbiException(
1053 "Expected application/yaml or application/json Content-Type",
1054 HTTPStatus.BAD_REQUEST,
1055 )
tiernoa5035702019-07-29 08:54:42 +00001056
1057 if method == "GET":
1058 token_info = self.authenticator.authorize()
1059 # for logging
1060 self._format_login(token_info)
1061 if token_id:
1062 outdata = self.authenticator.get_token(token_info, token_id)
tiernoc94c3df2018-02-09 15:38:54 +01001063 else:
tiernoa5035702019-07-29 08:54:42 +00001064 outdata = self.authenticator.get_token_list(token_info)
1065 elif method == "POST":
1066 try:
1067 token_info = self.authenticator.authorize()
1068 except Exception:
1069 token_info = None
1070 if kwargs:
1071 indata.update(kwargs)
1072 # This is needed to log the user when authentication fails
1073 cherrypy.request.login = "{}".format(indata.get("username", "-"))
garciadeblas4568a372021-03-24 09:19:48 +01001074 outdata = token_info = self.authenticator.new_token(
1075 token_info, indata, cherrypy.request.remote
1076 )
garciadeblasf2af4a12023-01-24 16:56:54 +01001077 cherrypy.session["Authorization"] = outdata["_id"] # pylint: disable=E1101
tiernoa5035702019-07-29 08:54:42 +00001078 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
1079 # for logging
1080 self._format_login(token_info)
selvi.ja9a1fc82022-04-04 06:54:30 +00001081 # password expiry check
1082 if self.authenticator.check_password_expiry(outdata):
garciadeblasf2af4a12023-01-24 16:56:54 +01001083 outdata = {
1084 "id": outdata["id"],
1085 "message": "change_password",
1086 "user_id": outdata["user_id"],
1087 }
tiernoa5035702019-07-29 08:54:42 +00001088 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1089 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
elumalai7802ff82023-04-24 20:38:32 +05301090 cef_event(
1091 cef_logger,
1092 {
1093 "name": "User Login",
1094 "sourceUserName": token_info.get("username"),
1095 "message": "User Logged In, Project={} Outcome=Success".format(
1096 token_info.get("project_name")
1097 ),
1098 },
1099 )
1100 cherrypy.log("{}".format(cef_logger))
tiernoa5035702019-07-29 08:54:42 +00001101 elif method == "DELETE":
1102 if not token_id and "id" in kwargs:
1103 token_id = kwargs["id"]
1104 elif not token_id:
1105 token_info = self.authenticator.authorize()
1106 # for logging
1107 self._format_login(token_info)
1108 token_id = token_info["_id"]
Rahulc72bc8e2023-12-05 11:54:38 +00001109 if current_backend != "keystone":
1110 token_details = self.engine.db.get_one("tokens", {"_id": token_id})
1111 current_user = token_details.get("username")
1112 current_project = token_details.get("project_name")
1113 else:
1114 current_user = "keystone backend"
1115 current_project = "keystone backend"
tiernoa5035702019-07-29 08:54:42 +00001116 outdata = self.authenticator.del_token(token_id)
1117 token_info = None
garciadeblasf2af4a12023-01-24 16:56:54 +01001118 cherrypy.session["Authorization"] = "logout" # pylint: disable=E1101
elumalai7802ff82023-04-24 20:38:32 +05301119 cef_event(
1120 cef_logger,
1121 {
1122 "name": "User Logout",
1123 "sourceUserName": current_user,
1124 "message": "User Logged Out, Project={} Outcome=Success".format(
1125 current_project
1126 ),
1127 },
1128 )
1129 cherrypy.log("{}".format(cef_logger))
tiernoa5035702019-07-29 08:54:42 +00001130 # cherrypy.response.cookie["Authorization"] = token_id
1131 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1132 else:
garciadeblas4568a372021-03-24 09:19:48 +01001133 raise NbiException(
1134 "Method {} not allowed for token".format(method),
1135 HTTPStatus.METHOD_NOT_ALLOWED,
1136 )
tiernoa5035702019-07-29 08:54:42 +00001137 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001138
1139 @cherrypy.expose
1140 def test(self, *args, **kwargs):
garciadeblas4568a372021-03-24 09:19:48 +01001141 if not cherrypy.config.get("server.enable_test") or (
1142 isinstance(cherrypy.config["server.enable_test"], str)
1143 and cherrypy.config["server.enable_test"].lower() == "false"
1144 ):
tierno4836bac2020-01-15 14:41:48 +00001145 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value
1146 return "test URL is disabled"
tiernoc94c3df2018-02-09 15:38:54 +01001147 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +01001148 if args and args[0] == "help":
garciadeblas4568a372021-03-24 09:19:48 +01001149 return (
1150 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1151 "sleep/<time>\nmessage/topic\n</pre></html>"
1152 )
tiernof27c79b2018-03-12 17:08:42 +01001153
1154 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +01001155 try:
1156 # self.engine.load_dbase(cherrypy.request.app.config)
garciadeblasf2af4a12023-01-24 16:56:54 +01001157 pid = self.authenticator.create_admin_project()
1158 self.authenticator.create_admin_user(pid)
tiernoc94c3df2018-02-09 15:38:54 +01001159 return "Done. User 'admin', password 'admin' created"
1160 except Exception:
1161 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1162 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +01001163 elif args and args[0] == "file":
garciadeblas4568a372021-03-24 09:19:48 +01001164 return cherrypy.lib.static.serve_file(
1165 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1],
1166 "text/plain",
1167 "attachment",
1168 )
tiernof27c79b2018-03-12 17:08:42 +01001169 elif args and args[0] == "file2":
garciadeblas4568a372021-03-24 09:19:48 +01001170 f_path = (
1171 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1]
1172 )
tiernof27c79b2018-03-12 17:08:42 +01001173 f = open(f_path, "r")
1174 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +01001175 return f
tierno55945e72018-04-06 16:40:27 +02001176
tiernof27c79b2018-03-12 17:08:42 +01001177 elif len(args) == 2 and args[0] == "db-clear":
tiernof717cbe2018-12-03 16:35:42 +00001178 deleted_info = self.engine.db.del_list(args[1], kwargs)
1179 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
1180 elif len(args) and args[0] == "fs-clear":
1181 if len(args) >= 2:
1182 folders = (args[1],)
1183 else:
1184 folders = self.engine.fs.dir_ls(".")
1185 for folder in folders:
1186 self.engine.fs.file_delete(folder)
1187 return ",".join(folders) + " folders deleted\n"
tiernoc94c3df2018-02-09 15:38:54 +01001188 elif args and args[0] == "login":
1189 if not cherrypy.request.headers.get("Authorization"):
garciadeblas4568a372021-03-24 09:19:48 +01001190 cherrypy.response.headers[
1191 "WWW-Authenticate"
1192 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
tiernoc94c3df2018-02-09 15:38:54 +01001193 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1194 elif args and args[0] == "login2":
1195 if not cherrypy.request.headers.get("Authorization"):
garciadeblas4568a372021-03-24 09:19:48 +01001196 cherrypy.response.headers[
1197 "WWW-Authenticate"
1198 ] = 'Bearer realm="Access to OSM site"'
tiernoc94c3df2018-02-09 15:38:54 +01001199 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1200 elif args and args[0] == "sleep":
1201 sleep_time = 5
1202 try:
1203 sleep_time = int(args[1])
1204 except Exception:
1205 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1206 return self._format_out("Database already initialized")
1207 thread_info = cherrypy.thread_data
1208 print(thread_info)
1209 time.sleep(sleep_time)
1210 # thread_info
1211 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +02001212 main_topic = args[1]
1213 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +01001214 try:
garciadeblas4568a372021-03-24 09:19:48 +01001215 if cherrypy.request.method == "POST":
garciadeblas4cd875d2023-02-14 19:05:34 +01001216 to_send = yaml.safe_load(cherrypy.request.body)
tierno55945e72018-04-06 16:40:27 +02001217 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +02001218 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +02001219 return_text += " {}: {}\n".format(k, v)
garciadeblas4568a372021-03-24 09:19:48 +01001220 elif cherrypy.request.method == "GET":
tierno55945e72018-04-06 16:40:27 +02001221 for k, v in kwargs.items():
garciadeblas4cd875d2023-02-14 19:05:34 +01001222 v_dict = yaml.safe_load(v)
tiernof1509b22020-05-12 14:32:37 +00001223 self.engine.msg.write(main_topic, k, v_dict)
1224 return_text += " {}: {}\n".format(k, v_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001225 except Exception as e:
tierno55945e72018-04-06 16:40:27 +02001226 return_text += "Error: " + str(e)
1227 return_text += "</pre></html>\n"
1228 return return_text
tiernoc94c3df2018-02-09 15:38:54 +01001229
1230 return_text = (
garciadeblas4568a372021-03-24 09:19:48 +01001231 "<html><pre>\nheaders:\n args: {}\n".format(args)
1232 + " kwargs: {}\n".format(kwargs)
1233 + " headers: {}\n".format(cherrypy.request.headers)
1234 + " path_info: {}\n".format(cherrypy.request.path_info)
1235 + " query_string: {}\n".format(cherrypy.request.query_string)
garciadeblasf2af4a12023-01-24 16:56:54 +01001236 + " session: {}\n".format(cherrypy.session) # pylint: disable=E1101
garciadeblas4568a372021-03-24 09:19:48 +01001237 + " cookie: {}\n".format(cherrypy.request.cookie)
1238 + " method: {}\n".format(cherrypy.request.method)
garciadeblasf2af4a12023-01-24 16:56:54 +01001239 + " session: {}\n".format(
1240 cherrypy.session.get("fieldname") # pylint: disable=E1101
1241 )
garciadeblas4568a372021-03-24 09:19:48 +01001242 + " body:\n"
1243 )
tiernoc94c3df2018-02-09 15:38:54 +01001244 return_text += " length: {}\n".format(cherrypy.request.body.length)
1245 if cherrypy.request.body.length:
1246 return_text += " content: {}\n".format(
garciadeblas4568a372021-03-24 09:19:48 +01001247 str(
1248 cherrypy.request.body.read(
1249 int(cherrypy.request.headers.get("Content-Length", 0))
1250 )
1251 )
1252 )
tiernoc94c3df2018-02-09 15:38:54 +01001253 if thread_info:
1254 return_text += "thread: {}\n".format(thread_info)
1255 return_text += "</pre></html>"
1256 return return_text
1257
tierno701018c2019-06-25 11:13:14 +00001258 @staticmethod
1259 def _check_valid_url_method(method, *args):
tiernof27c79b2018-03-12 17:08:42 +01001260 if len(args) < 3:
garciadeblas4568a372021-03-24 09:19:48 +01001261 raise NbiException(
1262 "URL must contain at least 'main_topic/version/topic'",
1263 HTTPStatus.METHOD_NOT_ALLOWED,
1264 )
tiernof27c79b2018-03-12 17:08:42 +01001265
tierno701018c2019-06-25 11:13:14 +00001266 reference = valid_url_methods
tiernof27c79b2018-03-12 17:08:42 +01001267 for arg in args:
1268 if arg is None:
1269 break
1270 if not isinstance(reference, dict):
garciadeblas4568a372021-03-24 09:19:48 +01001271 raise NbiException(
1272 "URL contains unexpected extra items '{}'".format(arg),
1273 HTTPStatus.METHOD_NOT_ALLOWED,
1274 )
tiernof27c79b2018-03-12 17:08:42 +01001275
1276 if arg in reference:
1277 reference = reference[arg]
1278 elif "<ID>" in reference:
1279 reference = reference["<ID>"]
1280 elif "*" in reference:
tierno74b53582020-06-18 10:52:37 +00001281 # if there is content
1282 if reference["*"]:
1283 reference = reference["*"]
tiernof27c79b2018-03-12 17:08:42 +01001284 break
1285 else:
garciadeblas4568a372021-03-24 09:19:48 +01001286 raise NbiException(
1287 "Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED
1288 )
tiernof27c79b2018-03-12 17:08:42 +01001289 if "TODO" in reference and method in reference["TODO"]:
garciadeblas4568a372021-03-24 09:19:48 +01001290 raise NbiException(
1291 "Method {} not supported yet for this URL".format(method),
1292 HTTPStatus.NOT_IMPLEMENTED,
1293 )
tierno2236d202018-05-16 19:05:16 +02001294 elif "METHODS" in reference and method not in reference["METHODS"]:
garciadeblas4568a372021-03-24 09:19:48 +01001295 raise NbiException(
1296 "Method {} not supported for this URL".format(method),
1297 HTTPStatus.METHOD_NOT_ALLOWED,
1298 )
tierno701018c2019-06-25 11:13:14 +00001299 return reference["ROLE_PERMISSION"] + method.lower()
tiernof27c79b2018-03-12 17:08:42 +01001300
1301 @staticmethod
tiernob24258a2018-10-04 18:39:49 +02001302 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +01001303 """
1304 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +02001305 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +01001306 :param version:
tiernob24258a2018-10-04 18:39:49 +02001307 :param topic:
tiernof27c79b2018-03-12 17:08:42 +01001308 :param id:
1309 :return: None
1310 """
1311 # 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 +01001312 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(
1313 main_topic, version, topic, id
1314 )
tiernof27c79b2018-03-12 17:08:42 +01001315 return
1316
tierno65ca36d2019-02-12 19:27:52 +01001317 @staticmethod
tierno701018c2019-06-25 11:13:14 +00001318 def _extract_query_string_operations(kwargs, method):
1319 """
1320
1321 :param kwargs:
1322 :return:
1323 """
1324 query_string_operations = []
1325 if kwargs:
1326 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1327 if qs in kwargs and kwargs[qs].lower() != "false":
1328 query_string_operations.append(qs.lower() + ":" + method.lower())
1329 return query_string_operations
1330
1331 @staticmethod
1332 def _manage_admin_query(token_info, kwargs, method, _id):
tierno65ca36d2019-02-12 19:27:52 +01001333 """
1334 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1335 Check that users has rights to use them and returs the admin_query
tierno701018c2019-06-25 11:13:14 +00001336 :param token_info: token_info rights obtained by token
tierno65ca36d2019-02-12 19:27:52 +01001337 :param kwargs: query string input.
1338 :param method: http method: GET, POSST, PUT, ...
1339 :param _id:
1340 :return: admin_query dictionary with keys:
1341 public: True, False or None
1342 force: True or False
1343 project_id: tuple with projects used for accessing an element
1344 set_project: tuple with projects that a created element will belong to
1345 method: show, list, delete, write
1346 """
garciadeblas4568a372021-03-24 09:19:48 +01001347 admin_query = {
1348 "force": False,
1349 "project_id": (token_info["project_id"],),
1350 "username": token_info["username"],
1351 "admin": token_info["admin"],
1352 "public": None,
1353 "allow_show_user_project_role": token_info["allow_show_user_project_role"],
1354 }
tierno65ca36d2019-02-12 19:27:52 +01001355 if kwargs:
1356 # FORCE
1357 if "FORCE" in kwargs:
garciadeblas4568a372021-03-24 09:19:48 +01001358 if (
1359 kwargs["FORCE"].lower() != "false"
1360 ): # if None or True set force to True
tierno65ca36d2019-02-12 19:27:52 +01001361 admin_query["force"] = True
1362 del kwargs["FORCE"]
1363 # PUBLIC
1364 if "PUBLIC" in kwargs:
garciadeblas4568a372021-03-24 09:19:48 +01001365 if (
1366 kwargs["PUBLIC"].lower() != "false"
1367 ): # if None or True set public to True
tierno65ca36d2019-02-12 19:27:52 +01001368 admin_query["public"] = True
1369 else:
1370 admin_query["public"] = False
1371 del kwargs["PUBLIC"]
1372 # ADMIN
1373 if "ADMIN" in kwargs:
1374 behave_as = kwargs.pop("ADMIN")
1375 if behave_as.lower() != "false":
tierno701018c2019-06-25 11:13:14 +00001376 if not token_info["admin"]:
garciadeblas4568a372021-03-24 09:19:48 +01001377 raise NbiException(
1378 "Only admin projects can use 'ADMIN' query string",
1379 HTTPStatus.UNAUTHORIZED,
1380 )
1381 if (
1382 not behave_as or behave_as.lower() == "true"
1383 ): # convert True, None to empty list
tierno65ca36d2019-02-12 19:27:52 +01001384 admin_query["project_id"] = ()
1385 elif isinstance(behave_as, (list, tuple)):
1386 admin_query["project_id"] = behave_as
garciadeblas4568a372021-03-24 09:19:48 +01001387 else: # isinstance(behave_as, str)
1388 admin_query["project_id"] = (behave_as,)
tierno65ca36d2019-02-12 19:27:52 +01001389 if "SET_PROJECT" in kwargs:
1390 set_project = kwargs.pop("SET_PROJECT")
1391 if not set_project:
1392 admin_query["set_project"] = list(admin_query["project_id"])
1393 else:
1394 if isinstance(set_project, str):
garciadeblas4568a372021-03-24 09:19:48 +01001395 set_project = (set_project,)
tierno65ca36d2019-02-12 19:27:52 +01001396 if admin_query["project_id"]:
1397 for p in set_project:
1398 if p not in admin_query["project_id"]:
garciadeblas4568a372021-03-24 09:19:48 +01001399 raise NbiException(
1400 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1401 "'ADMIN='{p}'".format(p=p),
1402 HTTPStatus.UNAUTHORIZED,
1403 )
tierno65ca36d2019-02-12 19:27:52 +01001404 admin_query["set_project"] = set_project
1405
1406 # PROJECT_READ
1407 # if "PROJECT_READ" in kwargs:
1408 # admin_query["project"] = kwargs.pop("project")
tierno701018c2019-06-25 11:13:14 +00001409 # if admin_query["project"] == token_info["project_id"]:
tierno65ca36d2019-02-12 19:27:52 +01001410 if method == "GET":
1411 if _id:
1412 admin_query["method"] = "show"
1413 else:
1414 admin_query["method"] = "list"
1415 elif method == "DELETE":
1416 admin_query["method"] = "delete"
1417 else:
1418 admin_query["method"] = "write"
1419 return admin_query
1420
tiernoc94c3df2018-02-09 15:38:54 +01001421 @cherrypy.expose
garciadeblas4568a372021-03-24 09:19:48 +01001422 def default(
1423 self,
1424 main_topic=None,
1425 version=None,
1426 topic=None,
1427 _id=None,
1428 item=None,
1429 *args,
1430 **kwargs
1431 ):
tierno701018c2019-06-25 11:13:14 +00001432 token_info = None
selvi.j9919bbc2023-04-26 12:22:13 +00001433 outdata = {}
tiernof27c79b2018-03-12 17:08:42 +01001434 _format = None
tierno0f98af52018-03-19 10:28:22 +01001435 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +02001436 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +02001437 rollback = []
tierno701018c2019-06-25 11:13:14 +00001438 engine_session = None
elumalai7802ff82023-04-24 20:38:32 +05301439 url_id = ""
1440 log_mapping = {
1441 "POST": "Creating",
1442 "GET": "Fetching",
1443 "DELETE": "Deleting",
1444 "PUT": "Updating",
1445 "PATCH": "Updating",
1446 }
tiernoc94c3df2018-02-09 15:38:54 +01001447 try:
tiernob24258a2018-10-04 18:39:49 +02001448 if not main_topic or not version or not topic:
garciadeblas4568a372021-03-24 09:19:48 +01001449 raise NbiException(
1450 "URL must contain at least 'main_topic/version/topic'",
1451 HTTPStatus.METHOD_NOT_ALLOWED,
1452 )
1453 if main_topic not in (
1454 "admin",
1455 "vnfpkgm",
1456 "nsd",
1457 "nslcm",
1458 "pdu",
1459 "nst",
1460 "nsilcm",
1461 "nspm",
almagiae47b9132022-05-17 14:12:22 +02001462 "vnflcm",
garciadeblas4568a372021-03-24 09:19:48 +01001463 ):
1464 raise NbiException(
1465 "URL main_topic '{}' not supported".format(main_topic),
1466 HTTPStatus.METHOD_NOT_ALLOWED,
1467 )
1468 if version != "v1":
1469 raise NbiException(
1470 "URL version '{}' not supported".format(version),
1471 HTTPStatus.METHOD_NOT_ALLOWED,
1472 )
elumalai7802ff82023-04-24 20:38:32 +05301473 if _id is not None:
1474 url_id = _id
tiernoc94c3df2018-02-09 15:38:54 +01001475
garciadeblas4568a372021-03-24 09:19:48 +01001476 if (
1477 kwargs
1478 and "METHOD" in kwargs
1479 and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1480 ):
tiernof27c79b2018-03-12 17:08:42 +01001481 method = kwargs.pop("METHOD")
1482 else:
1483 method = cherrypy.request.method
tierno65ca36d2019-02-12 19:27:52 +01001484
garciadeblas4568a372021-03-24 09:19:48 +01001485 role_permission = self._check_valid_url_method(
1486 method, main_topic, version, topic, _id, item, *args
1487 )
1488 query_string_operations = self._extract_query_string_operations(
1489 kwargs, method
1490 )
tiernob24258a2018-10-04 18:39:49 +02001491 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +01001492 return self.token(method, _id, kwargs)
garciadeblas4568a372021-03-24 09:19:48 +01001493 token_info = self.authenticator.authorize(
1494 role_permission, query_string_operations, _id
1495 )
tierno12eac3c2020-03-19 23:22:08 +00001496 if main_topic == "admin" and topic == "domains":
1497 return self.domain()
tierno701018c2019-06-25 11:13:14 +00001498 engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
tiernof27c79b2018-03-12 17:08:42 +01001499 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +02001500 engine_topic = topic
preethika.p329b8182020-04-22 12:25:39 +05301501
vijay.r35ef2f72019-04-30 17:55:49 +05301502 if item and topic != "pm_jobs":
tiernob24258a2018-10-04 18:39:49 +02001503 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +01001504
tiernob24258a2018-10-04 18:39:49 +02001505 if main_topic == "nsd":
1506 engine_topic = "nsds"
kayal2001f71c2e82024-06-25 15:26:24 +05301507 if topic == "ns_config_template":
1508 engine_topic = "nsconfigtemps"
tiernob24258a2018-10-04 18:39:49 +02001509 elif main_topic == "vnfpkgm":
1510 engine_topic = "vnfds"
delacruzramo271d2002019-12-02 21:00:37 +01001511 if topic == "vnfpkg_op_occs":
1512 engine_topic = "vnfpkgops"
1513 if topic == "vnf_packages" and item == "action":
1514 engine_topic = "vnfpkgops"
tiernob24258a2018-10-04 18:39:49 +02001515 elif main_topic == "nslcm":
1516 engine_topic = "nsrs"
1517 if topic == "ns_lcm_op_occs":
1518 engine_topic = "nslcmops"
1519 if topic == "vnfrs" or topic == "vnf_instances":
1520 engine_topic = "vnfrs"
almagiae47b9132022-05-17 14:12:22 +02001521 elif main_topic == "vnflcm":
1522 if topic == "vnf_lcm_op_occs":
1523 engine_topic = "vnflcmops"
garciadeblas9750c5a2018-10-15 16:20:35 +02001524 elif main_topic == "nst":
1525 engine_topic = "nsts"
1526 elif main_topic == "nsilcm":
1527 engine_topic = "nsis"
1528 if topic == "nsi_lcm_op_occs":
delacruzramoc061f562019-04-05 11:00:02 +02001529 engine_topic = "nsilcmops"
tiernob24258a2018-10-04 18:39:49 +02001530 elif main_topic == "pdu":
1531 engine_topic = "pdus"
garciadeblas4568a372021-03-24 09:19:48 +01001532 if (
1533 engine_topic == "vims"
1534 ): # TODO this is for backward compatibility, it will be removed in the future
tiernob24258a2018-10-04 18:39:49 +02001535 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +01001536
preethika.p329b8182020-04-22 12:25:39 +05301537 if topic == "subscriptions":
1538 engine_topic = main_topic + "_" + topic
1539
tiernoc94c3df2018-02-09 15:38:54 +01001540 if method == "GET":
garciadeblas4568a372021-03-24 09:19:48 +01001541 if item in (
1542 "nsd_content",
1543 "package_content",
1544 "artifacts",
1545 "vnfd",
1546 "nsd",
1547 "nst",
1548 "nst_content",
kayal2001f71c2e82024-06-25 15:26:24 +05301549 "ns_config_template",
garciadeblas4568a372021-03-24 09:19:48 +01001550 ):
garciadeblas9750c5a2018-10-15 16:20:35 +02001551 if item in ("vnfd", "nsd", "nst"):
tiernof27c79b2018-03-12 17:08:42 +01001552 path = "$DESCRIPTOR"
1553 elif args:
1554 path = args
tiernob24258a2018-10-04 18:39:49 +02001555 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +01001556 path = ()
1557 else:
1558 path = None
garciadeblas4568a372021-03-24 09:19:48 +01001559 file, _format = self.engine.get_file(
1560 engine_session,
1561 engine_topic,
1562 _id,
1563 path,
1564 cherrypy.request.headers.get("Accept"),
1565 )
tiernof27c79b2018-03-12 17:08:42 +01001566 outdata = file
1567 elif not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001568 outdata = self.engine.get_item_list(
1569 engine_session, engine_topic, kwargs, api_req=True
1570 )
tiernoc94c3df2018-02-09 15:38:54 +01001571 else:
vijay.r35ef2f72019-04-30 17:55:49 +05301572 if item == "reports":
1573 # TODO check that project_id (_id in this context) has permissions
1574 _id = args[0]
K Sai Kiran57589552021-01-27 21:38:34 +05301575 filter_q = None
1576 if "vcaStatusRefresh" in kwargs:
1577 filter_q = {"vcaStatusRefresh": kwargs["vcaStatusRefresh"]}
garciadeblasf2af4a12023-01-24 16:56:54 +01001578 outdata = self.engine.get_item(
1579 engine_session, engine_topic, _id, filter_q, True
1580 )
delacruzramo271d2002019-12-02 21:00:37 +01001581
tiernof27c79b2018-03-12 17:08:42 +01001582 elif method == "POST":
tiernobdebce92019-07-01 15:36:49 +00001583 cherrypy.response.status = HTTPStatus.CREATED.value
garciadeblas4568a372021-03-24 09:19:48 +01001584 if topic in (
1585 "ns_descriptors_content",
1586 "vnf_packages_content",
1587 "netslice_templates_content",
kayal2001f71c2e82024-06-25 15:26:24 +05301588 "ns_config_template",
garciadeblas4568a372021-03-24 09:19:48 +01001589 ):
tiernof27c79b2018-03-12 17:08:42 +01001590 _id = cherrypy.request.headers.get("Transaction-Id")
1591 if not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001592 _id, _ = self.engine.new_item(
1593 rollback,
1594 engine_session,
1595 engine_topic,
1596 {},
1597 None,
1598 cherrypy.request.headers,
1599 )
1600 completed = self.engine.upload_content(
1601 engine_session,
1602 engine_topic,
1603 _id,
1604 indata,
1605 kwargs,
1606 cherrypy.request.headers,
1607 )
tiernof27c79b2018-03-12 17:08:42 +01001608 if completed:
tiernob24258a2018-10-04 18:39:49 +02001609 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001610 else:
1611 cherrypy.response.headers["Transaction-Id"] = _id
1612 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +02001613 elif topic == "ns_instances_content":
1614 # creates NSR
garciadeblas4568a372021-03-24 09:19:48 +01001615 _id, _ = self.engine.new_item(
1616 rollback, engine_session, engine_topic, indata, kwargs
1617 )
tiernob24258a2018-10-04 18:39:49 +02001618 # creates nslcmop
1619 indata["lcmOperationType"] = "instantiate"
1620 indata["nsInstanceId"] = _id
garciadeblasf53612b2024-07-12 14:44:37 +02001621 nslcmop_id, nsName, _ = self.engine.new_item(
garciadeblas4568a372021-03-24 09:19:48 +01001622 rollback, engine_session, "nslcmops", indata, None
1623 )
tiernob24258a2018-10-04 18:39:49 +02001624 self._set_location_header(main_topic, version, topic, _id)
garciadeblasf53612b2024-07-12 14:44:37 +02001625 outdata = {"id": _id, "nslcmop_id": nslcmop_id, "nsName": nsName}
tiernob24258a2018-10-04 18:39:49 +02001626 elif topic == "ns_instances" and item:
1627 indata["lcmOperationType"] = item
1628 indata["nsInstanceId"] = _id
garciadeblasf53612b2024-07-12 14:44:37 +02001629 _id, nsName, _ = self.engine.new_item(
garciadeblas4568a372021-03-24 09:19:48 +01001630 rollback, engine_session, "nslcmops", indata, kwargs
1631 )
1632 self._set_location_header(
1633 main_topic, version, "ns_lcm_op_occs", _id
1634 )
garciadeblasf53612b2024-07-12 14:44:37 +02001635 outdata = {"id": _id, "nsName": nsName}
tierno65acb4d2018-04-06 16:42:40 +02001636 cherrypy.response.status = HTTPStatus.ACCEPTED.value
garciadeblas9750c5a2018-10-15 16:20:35 +02001637 elif topic == "netslice_instances_content":
Felipe Vicens07f31722018-10-29 15:16:44 +01001638 # creates NetSlice_Instance_record (NSIR)
garciadeblas4568a372021-03-24 09:19:48 +01001639 _id, _ = self.engine.new_item(
1640 rollback, engine_session, engine_topic, indata, kwargs
1641 )
Felipe Vicens07f31722018-10-29 15:16:44 +01001642 self._set_location_header(main_topic, version, topic, _id)
garciadeblas9750c5a2018-10-15 16:20:35 +02001643 indata["lcmOperationType"] = "instantiate"
Felipe Vicens126af572019-06-05 19:13:04 +02001644 indata["netsliceInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001645 nsilcmop_id, _ = self.engine.new_item(
1646 rollback, engine_session, "nsilcmops", indata, kwargs
1647 )
kuuse078f55e2019-05-16 19:24:21 +02001648 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
garciadeblas9750c5a2018-10-15 16:20:35 +02001649 elif topic == "netslice_instances" and item:
1650 indata["lcmOperationType"] = item
Felipe Vicens126af572019-06-05 19:13:04 +02001651 indata["netsliceInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001652 _id, _ = self.engine.new_item(
1653 rollback, engine_session, "nsilcmops", indata, kwargs
1654 )
1655 self._set_location_header(
1656 main_topic, version, "nsi_lcm_op_occs", _id
1657 )
garciadeblas9750c5a2018-10-15 16:20:35 +02001658 outdata = {"id": _id}
1659 cherrypy.response.status = HTTPStatus.ACCEPTED.value
delacruzramo271d2002019-12-02 21:00:37 +01001660 elif topic == "vnf_packages" and item == "action":
1661 indata["lcmOperationType"] = item
1662 indata["vnfPkgId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001663 _id, _ = self.engine.new_item(
1664 rollback, engine_session, "vnfpkgops", indata, kwargs
1665 )
1666 self._set_location_header(
1667 main_topic, version, "vnfpkg_op_occs", _id
1668 )
delacruzramo271d2002019-12-02 21:00:37 +01001669 outdata = {"id": _id}
1670 cherrypy.response.status = HTTPStatus.ACCEPTED.value
preethika.p329b8182020-04-22 12:25:39 +05301671 elif topic == "subscriptions":
garciadeblas4568a372021-03-24 09:19:48 +01001672 _id, _ = self.engine.new_item(
1673 rollback, engine_session, engine_topic, indata, kwargs
1674 )
preethika.p329b8182020-04-22 12:25:39 +05301675 self._set_location_header(main_topic, version, topic, _id)
1676 link = {}
1677 link["self"] = cherrypy.response.headers["Location"]
garciadeblas4568a372021-03-24 09:19:48 +01001678 outdata = {
1679 "id": _id,
1680 "filter": indata["filter"],
1681 "callbackUri": indata["CallbackUri"],
1682 "_links": link,
1683 }
preethika.p329b8182020-04-22 12:25:39 +05301684 cherrypy.response.status = HTTPStatus.CREATED.value
almagiae47b9132022-05-17 14:12:22 +02001685 elif topic == "vnf_instances" and item:
1686 indata["lcmOperationType"] = item
1687 indata["vnfInstanceId"] = _id
garciadeblasf53612b2024-07-12 14:44:37 +02001688 _id, nsName, _ = self.engine.new_item(
garciadeblasf2af4a12023-01-24 16:56:54 +01001689 rollback, engine_session, "vnflcmops", indata, kwargs
1690 )
1691 self._set_location_header(
1692 main_topic, version, "vnf_lcm_op_occs", _id
1693 )
garciadeblasf53612b2024-07-12 14:44:37 +02001694 outdata = {"id": _id, "nsName": nsName}
almagiae47b9132022-05-17 14:12:22 +02001695 cherrypy.response.status = HTTPStatus.ACCEPTED.value
Gabriel Cuba84a60df2023-10-30 14:01:54 -05001696 elif topic == "ns_lcm_op_occs" and item == "cancel":
1697 indata["nsLcmOpOccId"] = _id
1698 self.engine.cancel_item(
1699 rollback, engine_session, "nslcmops", indata, None
1700 )
1701 self._set_location_header(main_topic, version, topic, _id)
1702 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +01001703 else:
garciadeblas4568a372021-03-24 09:19:48 +01001704 _id, op_id = self.engine.new_item(
1705 rollback,
1706 engine_session,
1707 engine_topic,
1708 indata,
1709 kwargs,
1710 cherrypy.request.headers,
1711 )
tiernob24258a2018-10-04 18:39:49 +02001712 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001713 outdata = {"id": _id}
tiernobdebce92019-07-01 15:36:49 +00001714 if op_id:
1715 outdata["op_id"] = op_id
1716 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernob24258a2018-10-04 18:39:49 +02001717 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tierno09c073e2018-04-26 13:36:48 +02001718
tiernoc94c3df2018-02-09 15:38:54 +01001719 elif method == "DELETE":
1720 if not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001721 outdata = self.engine.del_item_list(
1722 engine_session, engine_topic, kwargs
1723 )
tierno09c073e2018-04-26 13:36:48 +02001724 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +01001725 else: # len(args) > 1
tierno22577432020-04-08 15:16:57 +00001726 # for NS NSI generate an operation
1727 op_id = None
tierno701018c2019-06-25 11:13:14 +00001728 if topic == "ns_instances_content" and not engine_session["force"]:
tiernob24258a2018-10-04 18:39:49 +02001729 nslcmop_desc = {
1730 "lcmOperationType": "terminate",
1731 "nsInstanceId": _id,
garciadeblas4568a372021-03-24 09:19:48 +01001732 "autoremove": True,
tiernob24258a2018-10-04 18:39:49 +02001733 }
garciadeblasf53612b2024-07-12 14:44:37 +02001734 op_id, nsName, _ = self.engine.new_item(
garciadeblas4568a372021-03-24 09:19:48 +01001735 rollback, engine_session, "nslcmops", nslcmop_desc, kwargs
1736 )
tierno22577432020-04-08 15:16:57 +00001737 if op_id:
garciadeblasf53612b2024-07-12 14:44:37 +02001738 outdata = {"_id": op_id, "nsName": nsName}
garciadeblas4568a372021-03-24 09:19:48 +01001739 elif (
1740 topic == "netslice_instances_content"
1741 and not engine_session["force"]
1742 ):
garciadeblas9750c5a2018-10-15 16:20:35 +02001743 nsilcmop_desc = {
1744 "lcmOperationType": "terminate",
Felipe Vicens126af572019-06-05 19:13:04 +02001745 "netsliceInstanceId": _id,
garciadeblas4568a372021-03-24 09:19:48 +01001746 "autoremove": True,
garciadeblas9750c5a2018-10-15 16:20:35 +02001747 }
garciadeblas4568a372021-03-24 09:19:48 +01001748 op_id, _ = self.engine.new_item(
1749 rollback, engine_session, "nsilcmops", nsilcmop_desc, None
1750 )
tierno22577432020-04-08 15:16:57 +00001751 if op_id:
1752 outdata = {"_id": op_id}
1753 # if there is not any deletion in process, delete
1754 if not op_id:
1755 op_id = self.engine.del_item(engine_session, engine_topic, _id)
1756 if op_id:
1757 outdata = {"op_id": op_id}
garciadeblas4568a372021-03-24 09:19:48 +01001758 cherrypy.response.status = (
1759 HTTPStatus.ACCEPTED.value
1760 if op_id
1761 else HTTPStatus.NO_CONTENT.value
1762 )
tierno09c073e2018-04-26 13:36:48 +02001763
tierno7ae10112018-05-18 14:36:02 +02001764 elif method in ("PUT", "PATCH"):
tiernobdebce92019-07-01 15:36:49 +00001765 op_id = None
tierno701018c2019-06-25 11:13:14 +00001766 if not indata and not kwargs and not engine_session.get("set_project"):
garciadeblas4568a372021-03-24 09:19:48 +01001767 raise NbiException(
1768 "Nothing to update. Provide payload and/or query string",
1769 HTTPStatus.BAD_REQUEST,
1770 )
1771 if (
kayal2001f71c2e82024-06-25 15:26:24 +05301772 item
1773 in (
1774 "nsd_content",
1775 "package_content",
1776 "nst_content",
1777 "template_content",
1778 )
garciadeblas4568a372021-03-24 09:19:48 +01001779 and method == "PUT"
1780 ):
1781 completed = self.engine.upload_content(
1782 engine_session,
1783 engine_topic,
1784 _id,
1785 indata,
1786 kwargs,
1787 cherrypy.request.headers,
1788 )
tiernof27c79b2018-03-12 17:08:42 +01001789 if not completed:
1790 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +01001791 else:
garciadeblas4568a372021-03-24 09:19:48 +01001792 op_id = self.engine.edit_item(
1793 engine_session, engine_topic, _id, indata, kwargs
1794 )
tiernobdebce92019-07-01 15:36:49 +00001795
1796 if op_id:
1797 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1798 outdata = {"op_id": op_id}
1799 else:
1800 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
1801 outdata = None
tiernoc94c3df2018-02-09 15:38:54 +01001802 else:
garciadeblas4568a372021-03-24 09:19:48 +01001803 raise NbiException(
1804 "Method {} not allowed".format(method),
1805 HTTPStatus.METHOD_NOT_ALLOWED,
1806 )
tiernoa6bb45d2019-06-14 09:45:39 +00001807
1808 # if Role information changes, it is needed to reload the information of roles
1809 if topic == "roles" and method != "GET":
1810 self.authenticator.load_operation_to_allowed_roles()
delacruzramoad682a52019-12-10 16:26:34 +01001811
garciadeblas4568a372021-03-24 09:19:48 +01001812 if (
1813 topic == "projects"
1814 and method == "DELETE"
1815 or topic in ["users", "roles"]
1816 and method in ["PUT", "PATCH", "DELETE"]
1817 ):
delacruzramoad682a52019-12-10 16:26:34 +01001818 self.authenticator.remove_token_from_cache()
1819
garciadeblasf53612b2024-07-12 14:44:37 +02001820 cef_event(
1821 cef_logger,
1822 {
1823 "name": "User Operation",
1824 "sourceUserName": token_info.get("username"),
1825 },
1826 )
1827 if topic == "ns_instances_content" and url_id:
1828 nsName = (
1829 outdata.get("name") if method == "GET" else outdata.get("nsName")
1830 )
elumalai7802ff82023-04-24 20:38:32 +05301831 cef_event(
1832 cef_logger,
1833 {
garciadeblasf53612b2024-07-12 14:44:37 +02001834 "message": "{} {}, nsName={}, nsdId={}, Project={} Outcome=Success".format(
1835 log_mapping[method],
1836 topic,
1837 nsName,
1838 outdata.get("id"),
1839 token_info.get("project_name"),
1840 ),
1841 },
1842 )
1843 cherrypy.log("{}".format(cef_logger))
1844 elif topic == "ns_instances_content" and method == "POST":
1845 cef_event(
1846 cef_logger,
1847 {
1848 "message": "{} {}, nsName={}, nsdId={}, Project={} Outcome=Success".format(
1849 log_mapping[method],
1850 topic,
1851 outdata.get("nsName"),
1852 outdata.get("id"),
1853 token_info.get("project_name"),
1854 ),
1855 },
1856 )
1857 cherrypy.log("{}".format(cef_logger))
1858 elif topic in ("ns_instances", "vnf_instances") and item:
1859 cef_event(
1860 cef_logger,
1861 {
1862 "message": "{} {}, nsName={}, nsdId={}, Project={} Outcome=Success".format(
1863 log_mapping[method],
1864 topic,
1865 outdata.get("nsName"),
1866 url_id,
1867 token_info.get("project_name"),
1868 ),
1869 },
1870 )
1871 cherrypy.log("{}".format(cef_logger))
1872 elif item is not None:
1873 cef_event(
1874 cef_logger,
1875 {
elumalai7802ff82023-04-24 20:38:32 +05301876 "message": "Performing {} operation on {} {}, Project={} Outcome=Success".format(
1877 item,
1878 topic,
1879 url_id,
1880 token_info.get("project_name"),
1881 ),
1882 },
1883 )
1884 cherrypy.log("{}".format(cef_logger))
1885 else:
1886 cef_event(
1887 cef_logger,
1888 {
elumalai7802ff82023-04-24 20:38:32 +05301889 "message": "{} {} {}, Project={} Outcome=Success".format(
1890 log_mapping[method],
1891 topic,
1892 url_id,
1893 token_info.get("project_name"),
1894 ),
1895 },
1896 )
1897 cherrypy.log("{}".format(cef_logger))
tierno701018c2019-06-25 11:13:14 +00001898 return self._format_out(outdata, token_info, _format)
tiernob24258a2018-10-04 18:39:49 +02001899 except Exception as e:
garciadeblas4568a372021-03-24 09:19:48 +01001900 if isinstance(
1901 e,
1902 (
1903 NbiException,
1904 EngineException,
1905 DbException,
1906 FsException,
1907 MsgException,
1908 AuthException,
1909 ValidationError,
1910 AuthconnException,
1911 ),
1912 ):
tiernob24258a2018-10-04 18:39:49 +02001913 http_code_value = cherrypy.response.status = e.http_code.value
1914 http_code_name = e.http_code.name
1915 cherrypy.log("Exception {}".format(e))
1916 else:
garciadeblas4568a372021-03-24 09:19:48 +01001917 http_code_value = (
1918 cherrypy.response.status
1919 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Felipe Vicense36ab852018-11-23 14:12:09 +01001920 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
tiernob24258a2018-10-04 18:39:49 +02001921 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +02001922 if hasattr(outdata, "close"): # is an open file
1923 outdata.close()
tiernob24258a2018-10-04 18:39:49 +02001924 error_text = str(e)
1925 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +02001926 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +02001927 try:
tiernocc103432018-10-19 14:10:35 +02001928 if rollback_item.get("operation") == "set":
garciadeblas4568a372021-03-24 09:19:48 +01001929 self.engine.db.set_one(
1930 rollback_item["topic"],
1931 {"_id": rollback_item["_id"]},
1932 rollback_item["content"],
1933 fail_on_empty=False,
1934 )
preethika.p329b8182020-04-22 12:25:39 +05301935 elif rollback_item.get("operation") == "del_list":
garciadeblas4568a372021-03-24 09:19:48 +01001936 self.engine.db.del_list(
1937 rollback_item["topic"],
1938 rollback_item["filter"],
garciadeblas4568a372021-03-24 09:19:48 +01001939 )
tiernocc103432018-10-19 14:10:35 +02001940 else:
garciadeblas4568a372021-03-24 09:19:48 +01001941 self.engine.db.del_one(
1942 rollback_item["topic"],
1943 {"_id": rollback_item["_id"]},
1944 fail_on_empty=False,
1945 )
tierno3ace63c2018-05-03 17:51:43 +02001946 except Exception as e2:
garciadeblas4568a372021-03-24 09:19:48 +01001947 rollback_error_text = "Rollback Exception {}: {}".format(
1948 rollback_item, e2
1949 )
tiernob24258a2018-10-04 18:39:49 +02001950 cherrypy.log(rollback_error_text)
1951 error_text += ". " + rollback_error_text
1952 # if isinstance(e, MsgException):
1953 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1954 # engine_topic[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +01001955 problem_details = {
tiernob24258a2018-10-04 18:39:49 +02001956 "code": http_code_name,
1957 "status": http_code_value,
1958 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +01001959 }
elumalai7802ff82023-04-24 20:38:32 +05301960 if item is not None and token_info is not None:
1961 cef_event(
1962 cef_logger,
1963 {
1964 "name": "User Operation",
1965 "sourceUserName": token_info.get("username", None),
1966 "message": "Performing {} operation on {} {}, Project={} Outcome=Failure".format(
1967 item,
1968 topic,
1969 url_id,
1970 token_info.get("project_name", None),
1971 ),
1972 "severity": "2",
1973 },
1974 )
1975 cherrypy.log("{}".format(cef_logger))
1976 elif token_info is not None:
1977 cef_event(
1978 cef_logger,
1979 {
1980 "name": "User Operation",
1981 "sourceUserName": token_info.get("username", None),
1982 "message": "{} {} {}, Project={} Outcome=Failure".format(
1983 item,
1984 topic,
1985 url_id,
1986 token_info.get("project_name", None),
1987 ),
1988 "severity": "2",
1989 },
1990 )
1991 cherrypy.log("{}".format(cef_logger))
tierno701018c2019-06-25 11:13:14 +00001992 return self._format_out(problem_details, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001993 # raise cherrypy.HTTPError(e.http_code.value, str(e))
tiernoa5035702019-07-29 08:54:42 +00001994 finally:
1995 if token_info:
1996 self._format_login(token_info)
1997 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
1998 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1999 if outdata.get(logging_id):
garciadeblas4568a372021-03-24 09:19:48 +01002000 cherrypy.request.login += ";{}={}".format(
2001 logging_id, outdata[logging_id][:36]
2002 )
tiernoc94c3df2018-02-09 15:38:54 +01002003
2004
tiernoc94c3df2018-02-09 15:38:54 +01002005def _start_service():
2006 """
2007 Callback function called when cherrypy.engine starts
2008 Override configuration with env variables
2009 Set database, storage, message configuration
2010 Init database with admin/admin user password
2011 """
tierno932499c2019-01-28 17:28:10 +00002012 global nbi_server
2013 global subscription_thread
elumalai7802ff82023-04-24 20:38:32 +05302014 global cef_logger
Rahulc72bc8e2023-12-05 11:54:38 +00002015 global current_backend
tiernoc94c3df2018-02-09 15:38:54 +01002016 cherrypy.log.error("Starting osm_nbi")
2017 # update general cherrypy configuration
2018 update_dict = {}
2019
garciadeblas4568a372021-03-24 09:19:48 +01002020 engine_config = cherrypy.tree.apps["/osm"].config
tiernoc94c3df2018-02-09 15:38:54 +01002021 for k, v in environ.items():
garciadeblas6d83f8f2023-06-19 22:34:49 +02002022 if k == "OSMNBI_USER_MANAGEMENT":
2023 feature_state = eval(v.title())
2024 engine_config["authentication"]["user_management"] = feature_state
Adurtid68526d2023-11-14 11:14:53 +00002025 elif k == "OSMNBI_PWD_EXPIRE_DAYS":
2026 pwd_expire_days = int(v)
2027 engine_config["authentication"]["pwd_expire_days"] = pwd_expire_days
2028 elif k == "OSMNBI_MAX_PWD_ATTEMPT":
2029 max_pwd_attempt = int(v)
2030 engine_config["authentication"]["max_pwd_attempt"] = max_pwd_attempt
2031 elif k == "OSMNBI_ACCOUNT_EXPIRE_DAYS":
2032 account_expire_days = int(v)
2033 engine_config["authentication"]["account_expire_days"] = account_expire_days
tiernoc94c3df2018-02-09 15:38:54 +01002034 if not k.startswith("OSMNBI_"):
2035 continue
tiernoe1281182018-05-22 12:24:36 +02002036 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +01002037 if not k2:
2038 continue
2039 try:
2040 # update static configuration
garciadeblas4568a372021-03-24 09:19:48 +01002041 if k == "OSMNBI_STATIC_DIR":
2042 engine_config["/static"]["tools.staticdir.dir"] = v
2043 engine_config["/static"]["tools.staticdir.on"] = True
2044 elif k == "OSMNBI_SOCKET_PORT" or k == "OSMNBI_SERVER_PORT":
2045 update_dict["server.socket_port"] = int(v)
2046 elif k == "OSMNBI_SOCKET_HOST" or k == "OSMNBI_SERVER_HOST":
2047 update_dict["server.socket_host"] = v
tiernof5298be2018-05-16 14:43:57 +02002048 elif k1 in ("server", "test", "auth", "log"):
garciadeblas4568a372021-03-24 09:19:48 +01002049 update_dict[k1 + "." + k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01002050 elif k1 in ("message", "database", "storage", "authentication"):
tiernof5298be2018-05-16 14:43:57 +02002051 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +01002052 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +01002053 engine_config[k1][k2] = int(v)
2054 else:
2055 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01002056
tiernoc94c3df2018-02-09 15:38:54 +01002057 except ValueError as e:
2058 cherrypy.log.error("Ignoring environ '{}': " + str(e))
2059 except Exception as e:
garciadeblasf2af4a12023-01-24 16:56:54 +01002060 cherrypy.log(
2061 "WARNING: skipping environ '{}' on exception '{}'".format(k, e)
2062 )
tiernoc94c3df2018-02-09 15:38:54 +01002063
2064 if update_dict:
2065 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +02002066 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +01002067
2068 # logging cherrypy
garciadeblas4568a372021-03-24 09:19:48 +01002069 log_format_simple = (
2070 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
2071 )
2072 log_formatter_simple = logging.Formatter(
2073 log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S"
2074 )
tiernoc94c3df2018-02-09 15:38:54 +01002075 logger_server = logging.getLogger("cherrypy.error")
2076 logger_access = logging.getLogger("cherrypy.access")
2077 logger_cherry = logging.getLogger("cherrypy")
2078 logger_nbi = logging.getLogger("nbi")
2079
tiernof5298be2018-05-16 14:43:57 +02002080 if "log.file" in engine_config["global"]:
garciadeblas4568a372021-03-24 09:19:48 +01002081 file_handler = logging.handlers.RotatingFileHandler(
2082 engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0
2083 )
tiernoc94c3df2018-02-09 15:38:54 +01002084 file_handler.setFormatter(log_formatter_simple)
2085 logger_cherry.addHandler(file_handler)
2086 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +02002087 # log always to standard output
garciadeblas4568a372021-03-24 09:19:48 +01002088 for format_, logger in {
2089 "nbi.server %(filename)s:%(lineno)s": logger_server,
2090 "nbi.access %(filename)s:%(lineno)s": logger_access,
2091 "%(name)s %(filename)s:%(lineno)s": logger_nbi,
2092 }.items():
tiernob24258a2018-10-04 18:39:49 +02002093 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
garciadeblas4568a372021-03-24 09:19:48 +01002094 log_formatter_cherry = logging.Formatter(
2095 log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S"
2096 )
tiernob24258a2018-10-04 18:39:49 +02002097 str_handler = logging.StreamHandler()
2098 str_handler.setFormatter(log_formatter_cherry)
2099 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +01002100
tiernof5298be2018-05-16 14:43:57 +02002101 if engine_config["global"].get("log.level"):
2102 logger_cherry.setLevel(engine_config["global"]["log.level"])
2103 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +01002104
2105 # logging other modules
garciadeblas4568a372021-03-24 09:19:48 +01002106 for k1, logname in {
2107 "message": "nbi.msg",
2108 "database": "nbi.db",
2109 "storage": "nbi.fs",
2110 }.items():
tiernoc94c3df2018-02-09 15:38:54 +01002111 engine_config[k1]["logger_name"] = logname
2112 logger_module = logging.getLogger(logname)
2113 if "logfile" in engine_config[k1]:
garciadeblas4568a372021-03-24 09:19:48 +01002114 file_handler = logging.handlers.RotatingFileHandler(
2115 engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0
2116 )
tiernoc94c3df2018-02-09 15:38:54 +01002117 file_handler.setFormatter(log_formatter_simple)
2118 logger_module.addHandler(file_handler)
2119 if "loglevel" in engine_config[k1]:
2120 logger_module.setLevel(engine_config[k1]["loglevel"])
2121 # TODO add more entries, e.g.: storage
garciadeblas4568a372021-03-24 09:19:48 +01002122 cherrypy.tree.apps["/osm"].root.engine.start(engine_config)
2123 cherrypy.tree.apps["/osm"].root.authenticator.start(engine_config)
2124 cherrypy.tree.apps["/osm"].root.engine.init_db(target_version=database_version)
2125 cherrypy.tree.apps["/osm"].root.authenticator.init_db(
2126 target_version=auth_database_version
2127 )
tiernobee508e2019-01-21 11:21:49 +00002128
elumalai7802ff82023-04-24 20:38:32 +05302129 cef_logger = cef_event_builder(engine_config["authentication"])
2130
tierno932499c2019-01-28 17:28:10 +00002131 # start subscriptions thread:
garciadeblas4568a372021-03-24 09:19:48 +01002132 subscription_thread = SubscriptionThread(
2133 config=engine_config, engine=nbi_server.engine
2134 )
tierno932499c2019-01-28 17:28:10 +00002135 subscription_thread.start()
2136 # Do not capture except SubscriptionException
2137
tiernob2e48bd2020-02-04 15:47:18 +00002138 backend = engine_config["authentication"]["backend"]
Rahulc72bc8e2023-12-05 11:54:38 +00002139 current_backend = backend
garciadeblas4568a372021-03-24 09:19:48 +01002140 cherrypy.log.error(
2141 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
2142 nbi_version, nbi_version_date, backend
2143 )
2144 )
tiernoc94c3df2018-02-09 15:38:54 +01002145
2146
2147def _stop_service():
2148 """
2149 Callback function called when cherrypy.engine stops
2150 TODO: Ending database connections.
2151 """
tierno932499c2019-01-28 17:28:10 +00002152 global subscription_thread
tierno65ca36d2019-02-12 19:27:52 +01002153 if subscription_thread:
2154 subscription_thread.terminate()
tierno932499c2019-01-28 17:28:10 +00002155 subscription_thread = None
garciadeblas4568a372021-03-24 09:19:48 +01002156 cherrypy.tree.apps["/osm"].root.engine.stop()
tiernoc94c3df2018-02-09 15:38:54 +01002157 cherrypy.log.error("Stopping osm_nbi")
2158
tierno2236d202018-05-16 19:05:16 +02002159
tiernof5298be2018-05-16 14:43:57 +02002160def nbi(config_file):
tierno932499c2019-01-28 17:28:10 +00002161 global nbi_server
tiernoc94c3df2018-02-09 15:38:54 +01002162 # conf = {
2163 # '/': {
2164 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
2165 # 'tools.sessions.on': True,
2166 # 'tools.response_headers.on': True,
2167 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
2168 # }
2169 # }
2170 # cherrypy.Server.ssl_module = 'builtin'
2171 # cherrypy.Server.ssl_certificate = "http/cert.pem"
2172 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
2173 # cherrypy.Server.thread_pool = 10
2174 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
2175
2176 # cherrypy.config.update({'tools.auth_basic.on': True,
2177 # 'tools.auth_basic.realm': 'localhost',
2178 # 'tools.auth_basic.checkpassword': validate_password})
tierno932499c2019-01-28 17:28:10 +00002179 nbi_server = Server()
garciadeblas4568a372021-03-24 09:19:48 +01002180 cherrypy.engine.subscribe("start", _start_service)
2181 cherrypy.engine.subscribe("stop", _stop_service)
2182 cherrypy.quickstart(nbi_server, "/osm", config_file)
tiernof5298be2018-05-16 14:43:57 +02002183
2184
2185def usage():
garciadeblas4568a372021-03-24 09:19:48 +01002186 print(
2187 """Usage: {} [options]
tiernof5298be2018-05-16 14:43:57 +02002188 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
2189 -h|--help: shows this help
garciadeblas4568a372021-03-24 09:19:48 +01002190 """.format(
2191 sys.argv[0]
2192 )
2193 )
tierno2236d202018-05-16 19:05:16 +02002194 # --log-socket-host HOST: send logs to this host")
2195 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +01002196
2197
garciadeblas4568a372021-03-24 09:19:48 +01002198if __name__ == "__main__":
tiernof5298be2018-05-16 14:43:57 +02002199 try:
2200 # load parameters and configuration
2201 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
2202 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
2203 config_file = None
2204 for o, a in opts:
2205 if o in ("-h", "--help"):
2206 usage()
2207 sys.exit()
2208 elif o in ("-c", "--config"):
2209 config_file = a
2210 # elif o == "--log-socket-port":
2211 # log_socket_port = a
2212 # elif o == "--log-socket-host":
2213 # log_socket_host = a
2214 # elif o == "--log-file":
2215 # log_file = a
2216 else:
2217 assert False, "Unhandled option"
2218 if config_file:
2219 if not path.isfile(config_file):
garciadeblas4568a372021-03-24 09:19:48 +01002220 print(
2221 "configuration file '{}' that not exist".format(config_file),
2222 file=sys.stderr,
2223 )
tiernof5298be2018-05-16 14:43:57 +02002224 exit(1)
2225 else:
garciadeblas4568a372021-03-24 09:19:48 +01002226 for config_file in (
2227 __file__[: __file__.rfind(".")] + ".cfg",
2228 "./nbi.cfg",
2229 "/etc/osm/nbi.cfg",
2230 ):
tiernof5298be2018-05-16 14:43:57 +02002231 if path.isfile(config_file):
2232 break
2233 else:
garciadeblas4568a372021-03-24 09:19:48 +01002234 print(
2235 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
2236 file=sys.stderr,
2237 )
tiernof5298be2018-05-16 14:43:57 +02002238 exit(1)
2239 nbi(config_file)
2240 except getopt.GetoptError as e:
2241 print(str(e), file=sys.stderr)
2242 # usage()
2243 exit(1)