blob: 9a16a1e98ecd364447b274f2c4b96efbf06d39e8 [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
tiernoc94c3df2018-02-09 15:38:54 +010063 /pnf_descriptors 5 5
64 /<pnfdInfoId> 5 5 5
65 /pnfd_content 5 5
tiernof27c79b2018-03-12 17:08:42 +010066 /subscriptions 5 5
67 /<subscriptionId> 5 X
tiernoc94c3df2018-02-09 15:38:54 +010068
69 /vnfpkgm/v1
tierno55945e72018-04-06 16:40:27 +020070 /vnf_packages_content O O
tierno2236d202018-05-16 19:05:16 +020071 /<vnfPkgId> O O
tiernoc94c3df2018-02-09 15:38:54 +010072 /vnf_packages O5 O5
73 /<vnfPkgId> O5 O5 5
tiernoc94c3df2018-02-09 15:38:54 +010074 /package_content O5 O5
75 /upload_from_uri X
tiernof27c79b2018-03-12 17:08:42 +010076 /vnfd O5
77 /artifacts[/<artifactPath>] O5
78 /subscriptions X X
79 /<subscriptionId> X X
tiernoc94c3df2018-02-09 15:38:54 +010080
81 /nslcm/v1
tiernof27c79b2018-03-12 17:08:42 +010082 /ns_instances_content O O
tierno2236d202018-05-16 19:05:16 +020083 /<nsInstanceId> O O
tiernof27c79b2018-03-12 17:08:42 +010084 /ns_instances 5 5
tierno95692442018-05-24 18:05:28 +020085 /<nsInstanceId> O5 O5
tierno65acb4d2018-04-06 16:42:40 +020086 instantiate O5
87 terminate O5
88 action O
89 scale O5
elumalai8e3806c2022-04-28 17:26:24 +053090 migrate O
aticig544a2ae2022-04-05 09:00:17 +030091 update 05
garciadeblas0964edf2022-02-11 00:43:44 +010092 heal O5
tiernoc94c3df2018-02-09 15:38:54 +010093 /ns_lcm_op_occs 5 5
94 /<nsLcmOpOccId> 5 5 5
Gabriel Cuba84a60df2023-10-30 14:01:54 -050095 cancel 05
tiernof759d822018-06-11 18:54:54 +020096 /vnf_instances (also vnfrs for compatibility) O
97 /<vnfInstanceId> O
tiernof27c79b2018-03-12 17:08:42 +010098 /subscriptions 5 5
99 /<subscriptionId> 5 X
garciadeblas9750c5a2018-10-15 16:20:35 +0200100
tiernocb83c942018-09-24 17:28:13 +0200101 /pdu/v1
tierno032916c2019-03-22 13:27:12 +0000102 /pdu_descriptors O O
tiernocb83c942018-09-24 17:28:13 +0200103 /<id> O O O O
garciadeblas9750c5a2018-10-15 16:20:35 +0200104
tiernof27c79b2018-03-12 17:08:42 +0100105 /admin/v1
106 /tokens O O
tierno2236d202018-05-16 19:05:16 +0200107 /<id> O O
tiernof27c79b2018-03-12 17:08:42 +0100108 /users O O
tiernocd54a4a2018-09-12 16:40:35 +0200109 /<id> O O O O
tiernof27c79b2018-03-12 17:08:42 +0100110 /projects O O
tierno2236d202018-05-16 19:05:16 +0200111 /<id> O O
tierno55ba2e62018-12-11 17:22:22 +0000112 /vim_accounts (also vims for compatibility) O O
113 /<id> O O O
114 /wim_accounts O O
tierno2236d202018-05-16 19:05:16 +0200115 /<id> O O O
tierno0f98af52018-03-19 10:28:22 +0100116 /sdns O O
tierno2236d202018-05-16 19:05:16 +0200117 /<id> O O O
delacruzramofe598fe2019-10-23 18:25:11 +0200118 /k8sclusters O O
119 /<id> O O O
120 /k8srepos O O
121 /<id> O O
Felipe Vicensb66b0412020-05-06 10:11:00 +0200122 /osmrepos O O
123 /<id> O O
tiernoc94c3df2018-02-09 15:38:54 +0100124
garciadeblas9750c5a2018-10-15 16:20:35 +0200125 /nst/v1 O O
126 /netslice_templates_content O O
127 /<nstInfoId> O O O O
128 /netslice_templates O O
129 /<nstInfoId> O O O
130 /nst_content O O
131 /nst O
132 /artifacts[/<artifactPath>] O
133 /subscriptions X X
134 /<subscriptionId> X X
135
136 /nsilcm/v1
137 /netslice_instances_content O O
138 /<SliceInstanceId> O O
139 /netslice_instances O O
140 /<SliceInstanceId> O O
141 instantiate O
142 terminate O
143 action O
144 /nsi_lcm_op_occs O O
145 /<nsiLcmOpOccId> O O O
146 /subscriptions X X
147 /<subscriptionId> X X
148
tierno2236d202018-05-16 19:05:16 +0200149query string:
150 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
tierno36ec8602018-11-02 17:27:11 +0100151 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
152 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
153 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
154 attrName := string
tierno2236d202018-05-16 19:05:16 +0200155 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
156 item of the array, that is, pass if any item of the array pass the filter.
157 It allows both ne and neq for not equal
158 TODO: 4.3.3 Attribute selectors
159 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
tiernoc94c3df2018-02-09 15:38:54 +0100160 (none) … same as “exclude_default”
161 all_fields … all attributes.
tierno2236d202018-05-16 19:05:16 +0200162 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
163 conditionally mandatory, and that are not provided in <list>.
164 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
165 are not conditionally mandatory, and that are provided in <list>.
166 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
167 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
168 the particular resource
169 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
170 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
171 present specification for the particular resource, but that are not part of <list>
tierno65ca36d2019-02-12 19:27:52 +0100172 Additionally it admits some administrator values:
173 FORCE: To force operations skipping dependency checkings
174 ADMIN: To act as an administrator or a different project
175 PUBLIC: To get public descriptors or set a descriptor as public
176 SET_PROJECT: To make a descriptor available for other project
beierlmbc5a5242022-05-17 21:25:29 -0400177
tiernoc94c3df2018-02-09 15:38:54 +0100178Header field name Reference Example Descriptions
179 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
180 This header field shall be present if the response is expected to have a non-empty message body.
181 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
182 This header field shall be present if the request has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200183 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
184 Details are specified in clause 4.5.3.
tiernoc94c3df2018-02-09 15:38:54 +0100185 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
186Header field name Reference Example Descriptions
187 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
188 This header field shall be present if the response has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200189 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
190 new resource has been created.
tiernoc94c3df2018-02-09 15:38:54 +0100191 This header field shall be present if the response status code is 201 or 3xx.
tierno2236d202018-05-16 19:05:16 +0200192 In the present document this header field is also used if the response status code is 202 and a new resource was
193 created.
194 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
195 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
196 token.
197 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
198 certain resources.
199 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
200 response, and the total length of the file.
tiernoc94c3df2018-02-09 15:38:54 +0100201 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
tiernoc94c3df2018-02-09 15:38:54 +0100202"""
203
tierno701018c2019-06-25 11:13:14 +0000204valid_query_string = ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
205# ^ Contains possible administrative query string words:
206# ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
207# (not owned by my session project).
208# PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
209# FORCE=True(by default)|False: Force edition/deletion operations
210# SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
211
212valid_url_methods = {
213 # contains allowed URL and methods, and the role_permission name
214 "admin": {
215 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100216 "tokens": {
217 "METHODS": ("GET", "POST", "DELETE"),
218 "ROLE_PERMISSION": "tokens:",
219 "<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"},
220 },
221 "users": {
222 "METHODS": ("GET", "POST"),
223 "ROLE_PERMISSION": "users:",
224 "<ID>": {
225 "METHODS": ("GET", "DELETE", "PATCH"),
226 "ROLE_PERMISSION": "users:id:",
227 },
228 },
229 "projects": {
230 "METHODS": ("GET", "POST"),
231 "ROLE_PERMISSION": "projects:",
232 "<ID>": {
233 "METHODS": ("GET", "DELETE", "PATCH"),
234 "ROLE_PERMISSION": "projects:id:",
235 },
236 },
237 "roles": {
238 "METHODS": ("GET", "POST"),
239 "ROLE_PERMISSION": "roles:",
240 "<ID>": {
241 "METHODS": ("GET", "DELETE", "PATCH"),
242 "ROLE_PERMISSION": "roles:id:",
243 },
244 },
245 "vims": {
246 "METHODS": ("GET", "POST"),
247 "ROLE_PERMISSION": "vims:",
248 "<ID>": {
249 "METHODS": ("GET", "DELETE", "PATCH"),
250 "ROLE_PERMISSION": "vims:id:",
251 },
252 },
253 "vim_accounts": {
254 "METHODS": ("GET", "POST"),
255 "ROLE_PERMISSION": "vim_accounts:",
256 "<ID>": {
257 "METHODS": ("GET", "DELETE", "PATCH"),
258 "ROLE_PERMISSION": "vim_accounts:id:",
259 },
260 },
261 "wim_accounts": {
262 "METHODS": ("GET", "POST"),
263 "ROLE_PERMISSION": "wim_accounts:",
264 "<ID>": {
265 "METHODS": ("GET", "DELETE", "PATCH"),
266 "ROLE_PERMISSION": "wim_accounts:id:",
267 },
268 },
269 "sdns": {
270 "METHODS": ("GET", "POST"),
271 "ROLE_PERMISSION": "sdn_controllers:",
272 "<ID>": {
273 "METHODS": ("GET", "DELETE", "PATCH"),
274 "ROLE_PERMISSION": "sdn_controllers:id:",
275 },
276 },
277 "k8sclusters": {
278 "METHODS": ("GET", "POST"),
279 "ROLE_PERMISSION": "k8sclusters:",
280 "<ID>": {
281 "METHODS": ("GET", "DELETE", "PATCH"),
282 "ROLE_PERMISSION": "k8sclusters:id:",
283 },
284 },
285 "vca": {
286 "METHODS": ("GET", "POST"),
287 "ROLE_PERMISSION": "vca:",
288 "<ID>": {
289 "METHODS": ("GET", "DELETE", "PATCH"),
290 "ROLE_PERMISSION": "vca:id:",
291 },
292 },
293 "k8srepos": {
294 "METHODS": ("GET", "POST"),
295 "ROLE_PERMISSION": "k8srepos:",
296 "<ID>": {
297 "METHODS": ("GET", "DELETE"),
298 "ROLE_PERMISSION": "k8srepos:id:",
299 },
300 },
301 "osmrepos": {
302 "METHODS": ("GET", "POST"),
303 "ROLE_PERMISSION": "osmrepos:",
304 "<ID>": {
305 "METHODS": ("GET", "DELETE", "PATCH"),
306 "ROLE_PERMISSION": "osmrepos:id:",
307 },
308 },
309 "domains": {
310 "METHODS": ("GET",),
311 "ROLE_PERMISSION": "domains:",
312 },
tierno701018c2019-06-25 11:13:14 +0000313 }
314 },
315 "pdu": {
316 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100317 "pdu_descriptors": {
318 "METHODS": ("GET", "POST"),
319 "ROLE_PERMISSION": "pduds:",
320 "<ID>": {
321 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
322 "ROLE_PERMISSION": "pduds:id:",
323 },
324 },
tierno701018c2019-06-25 11:13:14 +0000325 }
326 },
327 "nsd": {
328 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100329 "ns_descriptors_content": {
330 "METHODS": ("GET", "POST"),
331 "ROLE_PERMISSION": "nsds:",
332 "<ID>": {
333 "METHODS": ("GET", "PUT", "DELETE"),
334 "ROLE_PERMISSION": "nsds:id:",
335 },
336 },
337 "ns_descriptors": {
338 "METHODS": ("GET", "POST"),
339 "ROLE_PERMISSION": "nsds:",
340 "<ID>": {
341 "METHODS": ("GET", "DELETE", "PATCH"),
342 "ROLE_PERMISSION": "nsds:id:",
343 "nsd_content": {
344 "METHODS": ("GET", "PUT"),
345 "ROLE_PERMISSION": "nsds:id:content:",
346 },
347 "nsd": {
348 "METHODS": ("GET",), # descriptor inside package
349 "ROLE_PERMISSION": "nsds:id:content:",
350 },
351 "artifacts": {
352 "METHODS": ("GET",),
353 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
354 "*": None,
355 },
356 },
357 },
358 "pnf_descriptors": {
359 "TODO": ("GET", "POST"),
360 "<ID>": {
361 "TODO": ("GET", "DELETE", "PATCH"),
362 "pnfd_content": {"TODO": ("GET", "PUT")},
363 },
364 },
365 "subscriptions": {
366 "TODO": ("GET", "POST"),
367 "<ID>": {"TODO": ("GET", "DELETE")},
368 },
tierno701018c2019-06-25 11:13:14 +0000369 }
370 },
371 "vnfpkgm": {
372 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100373 "vnf_packages_content": {
374 "METHODS": ("GET", "POST"),
375 "ROLE_PERMISSION": "vnfds:",
376 "<ID>": {
377 "METHODS": ("GET", "PUT", "DELETE"),
378 "ROLE_PERMISSION": "vnfds:id:",
379 },
380 },
381 "vnf_packages": {
382 "METHODS": ("GET", "POST"),
383 "ROLE_PERMISSION": "vnfds:",
384 "<ID>": {
385 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
386 "ROLE_PERMISSION": "vnfds:id:",
387 "package_content": {
388 "METHODS": ("GET", "PUT"), # package
389 "ROLE_PERMISSION": "vnfds:id:",
390 "upload_from_uri": {
391 "METHODS": (),
392 "TODO": ("POST",),
393 "ROLE_PERMISSION": "vnfds:id:upload:",
394 },
395 },
396 "vnfd": {
397 "METHODS": ("GET",), # descriptor inside package
398 "ROLE_PERMISSION": "vnfds:id:content:",
399 },
400 "artifacts": {
401 "METHODS": ("GET",),
402 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
403 "*": None,
404 },
405 "action": {
406 "METHODS": ("POST",),
407 "ROLE_PERMISSION": "vnfds:id:action:",
408 },
409 },
410 },
411 "subscriptions": {
412 "TODO": ("GET", "POST"),
413 "<ID>": {"TODO": ("GET", "DELETE")},
414 },
415 "vnfpkg_op_occs": {
416 "METHODS": ("GET",),
417 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
418 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"},
419 },
tierno701018c2019-06-25 11:13:14 +0000420 }
421 },
422 "nslcm": {
423 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100424 "ns_instances_content": {
425 "METHODS": ("GET", "POST"),
426 "ROLE_PERMISSION": "ns_instances:",
427 "<ID>": {
428 "METHODS": ("GET", "DELETE"),
429 "ROLE_PERMISSION": "ns_instances:id:",
430 },
431 },
432 "ns_instances": {
433 "METHODS": ("GET", "POST"),
434 "ROLE_PERMISSION": "ns_instances:",
435 "<ID>": {
436 "METHODS": ("GET", "DELETE"),
437 "ROLE_PERMISSION": "ns_instances:id:",
garciadeblas0964edf2022-02-11 00:43:44 +0100438 "heal": {
439 "METHODS": ("POST",),
440 "ROLE_PERMISSION": "ns_instances:id:heal:",
441 },
garciadeblas4568a372021-03-24 09:19:48 +0100442 "scale": {
443 "METHODS": ("POST",),
444 "ROLE_PERMISSION": "ns_instances:id:scale:",
445 },
446 "terminate": {
447 "METHODS": ("POST",),
448 "ROLE_PERMISSION": "ns_instances:id:terminate:",
449 },
450 "instantiate": {
451 "METHODS": ("POST",),
452 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
453 },
elumalai8e3806c2022-04-28 17:26:24 +0530454 "migrate": {
455 "METHODS": ("POST",),
456 "ROLE_PERMISSION": "ns_instances:id:migrate:",
457 },
garciadeblas4568a372021-03-24 09:19:48 +0100458 "action": {
459 "METHODS": ("POST",),
460 "ROLE_PERMISSION": "ns_instances:id:action:",
461 },
aticig544a2ae2022-04-05 09:00:17 +0300462 "update": {
463 "METHODS": ("POST",),
464 "ROLE_PERMISSION": "ns_instances:id:update:",
465 },
govindarajul519da482022-04-29 19:05:22 +0530466 "verticalscale": {
467 "METHODS": ("POST",),
garciadeblasf2af4a12023-01-24 16:56:54 +0100468 "ROLE_PERMISSION": "ns_instances:id:verticalscale:",
469 },
garciadeblas4568a372021-03-24 09:19:48 +0100470 },
471 },
472 "ns_lcm_op_occs": {
473 "METHODS": ("GET",),
474 "ROLE_PERMISSION": "ns_instances:opps:",
475 "<ID>": {
476 "METHODS": ("GET",),
477 "ROLE_PERMISSION": "ns_instances:opps:id:",
Gabriel Cuba84a60df2023-10-30 14:01:54 -0500478 "cancel": {
479 "METHODS": ("POST",),
480 "ROLE_PERMISSION": "ns_instances:opps:cancel:",
481 },
garciadeblas4568a372021-03-24 09:19:48 +0100482 },
483 },
484 "vnfrs": {
485 "METHODS": ("GET",),
486 "ROLE_PERMISSION": "vnf_instances:",
487 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
488 },
489 "vnf_instances": {
490 "METHODS": ("GET",),
491 "ROLE_PERMISSION": "vnf_instances:",
492 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
493 },
494 "subscriptions": {
495 "METHODS": ("GET", "POST"),
496 "ROLE_PERMISSION": "ns_subscriptions:",
497 "<ID>": {
498 "METHODS": ("GET", "DELETE"),
499 "ROLE_PERMISSION": "ns_subscriptions:id:",
500 },
501 },
tierno701018c2019-06-25 11:13:14 +0000502 }
503 },
almagiae47b9132022-05-17 14:12:22 +0200504 "vnflcm": {
505 "v1": {
garciadeblasf2af4a12023-01-24 16:56:54 +0100506 "vnf_instances": {
507 "METHODS": ("GET", "POST"),
508 "ROLE_PERMISSION": "vnflcm_instances:",
509 "<ID>": {
510 "METHODS": ("GET", "DELETE"),
511 "ROLE_PERMISSION": "vnflcm_instances:id:",
512 "scale": {
513 "METHODS": ("POST",),
514 "ROLE_PERMISSION": "vnflcm_instances:id:scale:",
515 },
516 "terminate": {
517 "METHODS": ("POST",),
518 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:",
519 },
520 "instantiate": {
521 "METHODS": ("POST",),
522 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:",
523 },
524 },
525 },
526 "vnf_lcm_op_occs": {
527 "METHODS": ("GET",),
528 "ROLE_PERMISSION": "vnf_instances:opps:",
529 "<ID>": {
530 "METHODS": ("GET",),
531 "ROLE_PERMISSION": "vnf_instances:opps:id:",
532 },
533 },
534 "subscriptions": {
535 "METHODS": ("GET", "POST"),
536 "ROLE_PERMISSION": "vnflcm_subscriptions:",
537 "<ID>": {
538 "METHODS": ("GET", "DELETE"),
539 "ROLE_PERMISSION": "vnflcm_subscriptions:id:",
540 },
541 },
almagiae47b9132022-05-17 14:12:22 +0200542 }
543 },
tierno701018c2019-06-25 11:13:14 +0000544 "nst": {
545 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100546 "netslice_templates_content": {
547 "METHODS": ("GET", "POST"),
548 "ROLE_PERMISSION": "slice_templates:",
549 "<ID>": {
550 "METHODS": ("GET", "PUT", "DELETE"),
551 "ROLE_PERMISSION": "slice_templates:id:",
552 },
553 },
554 "netslice_templates": {
555 "METHODS": ("GET", "POST"),
556 "ROLE_PERMISSION": "slice_templates:",
557 "<ID>": {
558 "METHODS": ("GET", "DELETE"),
559 "TODO": ("PATCH",),
560 "ROLE_PERMISSION": "slice_templates:id:",
561 "nst_content": {
562 "METHODS": ("GET", "PUT"),
563 "ROLE_PERMISSION": "slice_templates:id:content:",
564 },
565 "nst": {
566 "METHODS": ("GET",), # descriptor inside package
567 "ROLE_PERMISSION": "slice_templates:id:content:",
568 },
569 "artifacts": {
570 "METHODS": ("GET",),
571 "ROLE_PERMISSION": "slice_templates:id:content:",
572 "*": None,
573 },
574 },
575 },
576 "subscriptions": {
577 "TODO": ("GET", "POST"),
578 "<ID>": {"TODO": ("GET", "DELETE")},
579 },
tierno701018c2019-06-25 11:13:14 +0000580 }
581 },
582 "nsilcm": {
583 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100584 "netslice_instances_content": {
585 "METHODS": ("GET", "POST"),
586 "ROLE_PERMISSION": "slice_instances:",
587 "<ID>": {
588 "METHODS": ("GET", "DELETE"),
589 "ROLE_PERMISSION": "slice_instances:id:",
590 },
591 },
592 "netslice_instances": {
593 "METHODS": ("GET", "POST"),
594 "ROLE_PERMISSION": "slice_instances:",
595 "<ID>": {
596 "METHODS": ("GET", "DELETE"),
597 "ROLE_PERMISSION": "slice_instances:id:",
598 "terminate": {
599 "METHODS": ("POST",),
600 "ROLE_PERMISSION": "slice_instances:id:terminate:",
601 },
602 "instantiate": {
603 "METHODS": ("POST",),
604 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
605 },
606 "action": {
607 "METHODS": ("POST",),
608 "ROLE_PERMISSION": "slice_instances:id:action:",
609 },
610 },
611 },
612 "nsi_lcm_op_occs": {
613 "METHODS": ("GET",),
614 "ROLE_PERMISSION": "slice_instances:opps:",
615 "<ID>": {
616 "METHODS": ("GET",),
617 "ROLE_PERMISSION": "slice_instances:opps:id:",
618 },
619 },
tierno701018c2019-06-25 11:13:14 +0000620 }
621 },
622 "nspm": {
623 "v1": {
624 "pm_jobs": {
625 "<ID>": {
626 "reports": {
garciadeblas4568a372021-03-24 09:19:48 +0100627 "<ID>": {
628 "METHODS": ("GET",),
629 "ROLE_PERMISSION": "reports:id:",
630 }
tierno701018c2019-06-25 11:13:14 +0000631 }
632 },
633 },
634 },
635 },
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000636 "nsfm": {
637 "v1": {
garciadeblasf2af4a12023-01-24 16:56:54 +0100638 "alarms": {
639 "METHODS": ("GET", "PATCH"),
640 "ROLE_PERMISSION": "alarms:",
641 "<ID>": {
642 "METHODS": ("GET", "PATCH"),
643 "ROLE_PERMISSION": "alarms:id:",
644 },
645 }
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000646 },
647 },
tierno701018c2019-06-25 11:13:14 +0000648}
649
tiernoc94c3df2018-02-09 15:38:54 +0100650
651class NbiException(Exception):
tiernoc94c3df2018-02-09 15:38:54 +0100652 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
653 Exception.__init__(self, message)
654 self.http_code = http_code
655
656
657class Server(object):
658 instance = 0
659 # to decode bytes to str
660 reader = getreader("utf-8")
661
662 def __init__(self):
663 self.instance += 1
tierno701018c2019-06-25 11:13:14 +0000664 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
delacruzramoad682a52019-12-10 16:26:34 +0100665 self.engine = Engine(self.authenticator)
tiernoc94c3df2018-02-09 15:38:54 +0100666
tiernoc94c3df2018-02-09 15:38:54 +0100667 def _format_in(self, kwargs):
garciadeblasf2af4a12023-01-24 16:56:54 +0100668 error_text = "" # error_text must be initialized outside try
tiernoc94c3df2018-02-09 15:38:54 +0100669 try:
670 indata = None
671 if cherrypy.request.body.length:
672 error_text = "Invalid input format "
673
674 if "Content-Type" in cherrypy.request.headers:
675 if "application/json" in cherrypy.request.headers["Content-Type"]:
676 error_text = "Invalid json format "
677 indata = json.load(self.reader(cherrypy.request.body))
gcalvinode4adfe2018-10-30 11:46:09 +0100678 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100679 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
680 error_text = "Invalid yaml format "
garciadeblas4cd875d2023-02-14 19:05:34 +0100681 indata = yaml.safe_load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100682 cherrypy.request.headers.pop("Content-File-MD5", None)
garciadeblas4568a372021-03-24 09:19:48 +0100683 elif (
684 "application/binary" in cherrypy.request.headers["Content-Type"]
685 or "application/gzip"
686 in cherrypy.request.headers["Content-Type"]
687 or "application/zip" in cherrypy.request.headers["Content-Type"]
688 or "text/plain" in cherrypy.request.headers["Content-Type"]
689 ):
tiernof27c79b2018-03-12 17:08:42 +0100690 indata = cherrypy.request.body # .read()
garciadeblas4568a372021-03-24 09:19:48 +0100691 elif (
692 "multipart/form-data"
693 in cherrypy.request.headers["Content-Type"]
694 ):
tiernoc94c3df2018-02-09 15:38:54 +0100695 if "descriptor_file" in kwargs:
696 filecontent = kwargs.pop("descriptor_file")
697 if not filecontent.file:
garciadeblas4568a372021-03-24 09:19:48 +0100698 raise NbiException(
699 "empty file or content", HTTPStatus.BAD_REQUEST
700 )
tiernof27c79b2018-03-12 17:08:42 +0100701 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100702 if filecontent.content_type.value:
garciadeblas4568a372021-03-24 09:19:48 +0100703 cherrypy.request.headers[
704 "Content-Type"
705 ] = filecontent.content_type.value
tiernoc94c3df2018-02-09 15:38:54 +0100706 else:
707 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
708 # "Only 'Content-Type' of type 'application/json' or
709 # 'application/yaml' for input format are available")
710 error_text = "Invalid yaml format "
garciadeblas4cd875d2023-02-14 19:05:34 +0100711 indata = yaml.safe_load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100712 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100713 else:
714 error_text = "Invalid yaml format "
garciadeblas4cd875d2023-02-14 19:05:34 +0100715 indata = yaml.safe_load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100716 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100717 if not indata:
718 indata = {}
719
tiernoc94c3df2018-02-09 15:38:54 +0100720 format_yaml = False
721 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
722 format_yaml = True
723
724 for k, v in kwargs.items():
725 if isinstance(v, str):
726 if v == "":
727 kwargs[k] = None
728 elif format_yaml:
729 try:
garciadeblas4cd875d2023-02-14 19:05:34 +0100730 kwargs[k] = yaml.safe_load(v)
tiernoe1281182018-05-22 12:24:36 +0200731 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100732 pass
garciadeblas4568a372021-03-24 09:19:48 +0100733 elif (
734 k.endswith(".gt")
735 or k.endswith(".lt")
736 or k.endswith(".gte")
737 or k.endswith(".lte")
738 ):
tiernoc94c3df2018-02-09 15:38:54 +0100739 try:
740 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200741 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100742 try:
743 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200744 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100745 pass
746 elif v.find(",") > 0:
747 kwargs[k] = v.split(",")
748 elif isinstance(v, (list, tuple)):
749 for index in range(0, len(v)):
750 if v[index] == "":
751 v[index] = None
752 elif format_yaml:
753 try:
garciadeblas4cd875d2023-02-14 19:05:34 +0100754 v[index] = yaml.safe_load(v[index])
tiernoe1281182018-05-22 12:24:36 +0200755 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100756 pass
757
tiernof27c79b2018-03-12 17:08:42 +0100758 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100759 except (ValueError, yaml.YAMLError) as exc:
760 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
761 except KeyError as exc:
garciadeblas4568a372021-03-24 09:19:48 +0100762 raise NbiException(
763 "Query string error: " + str(exc), HTTPStatus.BAD_REQUEST
764 )
tiernob92094f2018-05-11 13:44:22 +0200765 except Exception as exc:
766 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100767
768 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000769 def _format_out(data, token_info=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100770 """
771 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100772 :param data: response to be sent. Can be a dict, text or file
tierno701018c2019-06-25 11:13:14 +0000773 :param token_info: Contains among other username and project
tierno5792d7d2019-08-30 15:37:12 +0000774 :param _format: The format to be set as Content-Type if data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100775 :return: None
776 """
tierno0f98af52018-03-19 10:28:22 +0100777 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100778 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100779 if accept and "text/html" in accept:
garciadeblas4568a372021-03-24 09:19:48 +0100780 return html.format(
781 data, cherrypy.request, cherrypy.response, token_info
782 )
tierno09c073e2018-04-26 13:36:48 +0200783 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100784 return
785 elif hasattr(data, "read"): # file object
786 if _format:
787 cherrypy.response.headers["Content-Type"] = _format
788 elif "b" in data.mode: # binariy asssumig zip
garciadeblas4568a372021-03-24 09:19:48 +0100789 cherrypy.response.headers["Content-Type"] = "application/zip"
tiernof27c79b2018-03-12 17:08:42 +0100790 else:
garciadeblas4568a372021-03-24 09:19:48 +0100791 cherrypy.response.headers["Content-Type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100792 # TODO check that cherrypy close file. If not implement pending things to close per thread next
793 return data
tierno0f98af52018-03-19 10:28:22 +0100794 if accept:
Frank Bryden02e700c2020-06-03 13:34:16 +0000795 if "text/html" in accept:
garciadeblas4568a372021-03-24 09:19:48 +0100796 return html.format(
797 data, cherrypy.request, cherrypy.response, token_info
798 )
Frank Brydenb5422da2020-08-10 11:44:11 +0000799 elif "application/yaml" in accept or "*/*" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100800 pass
garciadeblas4568a372021-03-24 09:19:48 +0100801 elif "application/json" in accept or (
802 cherrypy.response.status and cherrypy.response.status >= 300
803 ):
804 cherrypy.response.headers[
805 "Content-Type"
806 ] = "application/json; charset=utf-8"
Frank Bryden02e700c2020-06-03 13:34:16 +0000807 a = json.dumps(data, indent=4) + "\n"
garciadeblas4568a372021-03-24 09:19:48 +0100808 return a.encode("utf8")
809 cherrypy.response.headers["Content-Type"] = "application/yaml"
810 return yaml.safe_dump(
811 data,
812 explicit_start=True,
813 indent=4,
814 default_flow_style=False,
815 tags=False,
816 encoding="utf-8",
817 allow_unicode=True,
818 ) # , canonical=True, default_style='"'
tiernoc94c3df2018-02-09 15:38:54 +0100819
820 @cherrypy.expose
821 def index(self, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000822 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100823 try:
824 if cherrypy.request.method == "GET":
tierno701018c2019-06-25 11:13:14 +0000825 token_info = self.authenticator.authorize()
garciadeblas4568a372021-03-24 09:19:48 +0100826 outdata = token_info # Home page
tiernoc94c3df2018-02-09 15:38:54 +0100827 else:
garciadeblas4568a372021-03-24 09:19:48 +0100828 raise cherrypy.HTTPError(
829 HTTPStatus.METHOD_NOT_ALLOWED.value,
830 "Method {} not allowed for tokens".format(cherrypy.request.method),
831 )
tiernoc94c3df2018-02-09 15:38:54 +0100832
tierno701018c2019-06-25 11:13:14 +0000833 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100834
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100835 except (EngineException, AuthException) as e:
tierno701018c2019-06-25 11:13:14 +0000836 # cherrypy.log("index Exception {}".format(e))
tiernoc94c3df2018-02-09 15:38:54 +0100837 cherrypy.response.status = e.http_code.value
tierno701018c2019-06-25 11:13:14 +0000838 return self._format_out("Welcome to OSM!", token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100839
840 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200841 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200842 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200843 try:
844 if cherrypy.request.method != "GET":
garciadeblas4568a372021-03-24 09:19:48 +0100845 raise NbiException(
846 "Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED
847 )
tierno55945e72018-04-06 16:40:27 +0200848 elif args or kwargs:
garciadeblas4568a372021-03-24 09:19:48 +0100849 raise NbiException(
850 "Invalid URL or query string for version",
851 HTTPStatus.METHOD_NOT_ALLOWED,
852 )
tierno9c630112019-08-29 14:21:41 +0000853 # TODO include version of other modules, pick up from some kafka admin message
tierno5792d7d2019-08-30 15:37:12 +0000854 osm_nbi_version = {"version": nbi_version, "date": nbi_version_date}
855 return self._format_out(osm_nbi_version)
tierno55945e72018-04-06 16:40:27 +0200856 except NbiException as e:
857 cherrypy.response.status = e.http_code.value
858 problem_details = {
859 "code": e.http_code.name,
860 "status": e.http_code.value,
861 "detail": str(e),
862 }
863 return self._format_out(problem_details, None)
864
tierno12eac3c2020-03-19 23:22:08 +0000865 def domain(self):
866 try:
867 domains = {
garciadeblas4568a372021-03-24 09:19:48 +0100868 "user_domain_name": cherrypy.tree.apps["/osm"]
869 .config["authentication"]
870 .get("user_domain_name"),
871 "project_domain_name": cherrypy.tree.apps["/osm"]
872 .config["authentication"]
873 .get("project_domain_name"),
874 }
tierno12eac3c2020-03-19 23:22:08 +0000875 return self._format_out(domains)
876 except NbiException as e:
877 cherrypy.response.status = e.http_code.value
878 problem_details = {
879 "code": e.http_code.name,
880 "status": e.http_code.value,
881 "detail": str(e),
882 }
883 return self._format_out(problem_details, None)
884
tiernoa5035702019-07-29 08:54:42 +0000885 @staticmethod
886 def _format_login(token_info):
887 """
888 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
889 log this information
890 :param token_info: Dictionary with token content
891 :return: None
892 """
893 cherrypy.request.login = token_info.get("username", "-")
894 if token_info.get("project_name"):
895 cherrypy.request.login += "/" + token_info["project_name"]
896 if token_info.get("id"):
897 cherrypy.request.login += ";session=" + token_info["id"][0:12]
898
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000899 # NS Fault Management
900 @cherrypy.expose
garciadeblasf2af4a12023-01-24 16:56:54 +0100901 def nsfm(
902 self,
903 version=None,
904 topic=None,
905 uuid=None,
906 project_name=None,
907 ns_id=None,
908 *args,
909 **kwargs
910 ):
911 if topic == "alarms":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000912 try:
913 method = cherrypy.request.method
garciadeblasf2af4a12023-01-24 16:56:54 +0100914 role_permission = self._check_valid_url_method(
915 method, "nsfm", version, topic, None, None, *args
916 )
917 query_string_operations = self._extract_query_string_operations(
918 kwargs, method
919 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000920
garciadeblasf2af4a12023-01-24 16:56:54 +0100921 self.authenticator.authorize(
922 role_permission, query_string_operations, None
923 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000924
925 # to handle get request
garciadeblasf2af4a12023-01-24 16:56:54 +0100926 if cherrypy.request.method == "GET":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000927 # if request is on basis of uuid
garciadeblasf2af4a12023-01-24 16:56:54 +0100928 if uuid and uuid != "None":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000929 try:
930 alarm = self.engine.db.get_one("alarms", {"uuid": uuid})
garciadeblasf2af4a12023-01-24 16:56:54 +0100931 alarm_action = self.engine.db.get_one(
932 "alarms_action", {"uuid": uuid}
933 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000934 alarm.update(alarm_action)
garciadeblasf2af4a12023-01-24 16:56:54 +0100935 vnf = self.engine.db.get_one(
936 "vnfrs", {"nsr-id-ref": alarm["tags"]["ns_id"]}
937 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000938 alarm["vnf-id"] = vnf["_id"]
939 return self._format_out(str(alarm))
940 except Exception:
941 return self._format_out("Please provide valid alarm uuid")
garciadeblasf2af4a12023-01-24 16:56:54 +0100942 elif ns_id and ns_id != "None":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000943 # if request is on basis of ns_id
944 try:
garciadeblasf2af4a12023-01-24 16:56:54 +0100945 alarms = self.engine.db.get_list(
946 "alarms", {"tags.ns_id": ns_id}
947 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000948 for alarm in alarms:
garciadeblasf2af4a12023-01-24 16:56:54 +0100949 alarm_action = self.engine.db.get_one(
950 "alarms_action", {"uuid": alarm["uuid"]}
951 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000952 alarm.update(alarm_action)
953 return self._format_out(str(alarms))
954 except Exception:
955 return self._format_out("Please provide valid ns id")
956 else:
957 # to return only alarm which are related to given project
garciadeblasf2af4a12023-01-24 16:56:54 +0100958 project = self.engine.db.get_one(
959 "projects", {"name": project_name}
960 )
961 project_id = project.get("_id")
962 ns_list = self.engine.db.get_list(
963 "nsrs", {"_admin.projects_read": project_id}
964 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000965 ns_ids = []
966 for ns in ns_list:
967 ns_ids.append(ns.get("_id"))
968 alarms = self.engine.db.get_list("alarms")
garciadeblasf2af4a12023-01-24 16:56:54 +0100969 alarm_list = [
970 alarm
971 for alarm in alarms
972 if alarm["tags"]["ns_id"] in ns_ids
973 ]
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000974 for alrm in alarm_list:
garciadeblasf2af4a12023-01-24 16:56:54 +0100975 action = self.engine.db.get_one(
976 "alarms_action", {"uuid": alrm.get("uuid")}
977 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000978 alrm.update(action)
979 return self._format_out(str(alarm_list))
980 # to handle patch request for alarm update
garciadeblasf2af4a12023-01-24 16:56:54 +0100981 elif cherrypy.request.method == "PATCH":
garciadeblas4cd875d2023-02-14 19:05:34 +0100982 data = yaml.safe_load(cherrypy.request.body)
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000983 try:
984 # check if uuid is valid
985 self.engine.db.get_one("alarms", {"uuid": data.get("uuid")})
986 except Exception:
987 return self._format_out("Please provide valid alarm uuid.")
988 if data.get("is_enable") is not None:
989 if data.get("is_enable"):
garciadeblasf2af4a12023-01-24 16:56:54 +0100990 alarm_status = "ok"
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000991 else:
garciadeblasf2af4a12023-01-24 16:56:54 +0100992 alarm_status = "disabled"
993 self.engine.db.set_one(
994 "alarms",
995 {"uuid": data.get("uuid")},
996 {"alarm_status": alarm_status},
997 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000998 else:
garciadeblasf2af4a12023-01-24 16:56:54 +0100999 self.engine.db.set_one(
1000 "alarms",
1001 {"uuid": data.get("uuid")},
1002 {"threshold": data.get("threshold")},
1003 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001004 return self._format_out("Alarm updated")
1005 except Exception as e:
garciadeblasf2af4a12023-01-24 16:56:54 +01001006 if isinstance(
1007 e,
1008 (
1009 NbiException,
1010 EngineException,
1011 DbException,
1012 FsException,
1013 MsgException,
1014 AuthException,
1015 ValidationError,
1016 AuthconnException,
1017 ),
1018 ):
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001019 http_code_value = cherrypy.response.status = e.http_code.value
1020 http_code_name = e.http_code.name
1021 cherrypy.log("Exception {}".format(e))
1022 else:
garciadeblasf2af4a12023-01-24 16:56:54 +01001023 http_code_value = (
1024 cherrypy.response.status
1025 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001026 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
1027 http_code_name = HTTPStatus.BAD_REQUEST.name
1028 problem_details = {
1029 "code": http_code_name,
1030 "status": http_code_value,
1031 "detail": str(e),
1032 }
1033 return self._format_out(problem_details)
1034
tierno55945e72018-04-06 16:40:27 +02001035 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +01001036 def token(self, method, token_id=None, kwargs=None):
tierno701018c2019-06-25 11:13:14 +00001037 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +01001038 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +01001039 indata = self._format_in(kwargs)
1040 if not isinstance(indata, dict):
garciadeblas4568a372021-03-24 09:19:48 +01001041 raise NbiException(
1042 "Expected application/yaml or application/json Content-Type",
1043 HTTPStatus.BAD_REQUEST,
1044 )
tiernoa5035702019-07-29 08:54:42 +00001045
1046 if method == "GET":
1047 token_info = self.authenticator.authorize()
1048 # for logging
1049 self._format_login(token_info)
1050 if token_id:
1051 outdata = self.authenticator.get_token(token_info, token_id)
tiernoc94c3df2018-02-09 15:38:54 +01001052 else:
tiernoa5035702019-07-29 08:54:42 +00001053 outdata = self.authenticator.get_token_list(token_info)
1054 elif method == "POST":
1055 try:
1056 token_info = self.authenticator.authorize()
1057 except Exception:
1058 token_info = None
1059 if kwargs:
1060 indata.update(kwargs)
1061 # This is needed to log the user when authentication fails
1062 cherrypy.request.login = "{}".format(indata.get("username", "-"))
garciadeblas4568a372021-03-24 09:19:48 +01001063 outdata = token_info = self.authenticator.new_token(
1064 token_info, indata, cherrypy.request.remote
1065 )
garciadeblasf2af4a12023-01-24 16:56:54 +01001066 cherrypy.session["Authorization"] = outdata["_id"] # pylint: disable=E1101
tiernoa5035702019-07-29 08:54:42 +00001067 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
1068 # for logging
1069 self._format_login(token_info)
selvi.ja9a1fc82022-04-04 06:54:30 +00001070 # password expiry check
1071 if self.authenticator.check_password_expiry(outdata):
garciadeblasf2af4a12023-01-24 16:56:54 +01001072 outdata = {
1073 "id": outdata["id"],
1074 "message": "change_password",
1075 "user_id": outdata["user_id"],
1076 }
tiernoa5035702019-07-29 08:54:42 +00001077 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1078 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
elumalai7802ff82023-04-24 20:38:32 +05301079 cef_event(
1080 cef_logger,
1081 {
1082 "name": "User Login",
1083 "sourceUserName": token_info.get("username"),
1084 "message": "User Logged In, Project={} Outcome=Success".format(
1085 token_info.get("project_name")
1086 ),
1087 },
1088 )
1089 cherrypy.log("{}".format(cef_logger))
tiernoa5035702019-07-29 08:54:42 +00001090 elif method == "DELETE":
1091 if not token_id and "id" in kwargs:
1092 token_id = kwargs["id"]
1093 elif not token_id:
1094 token_info = self.authenticator.authorize()
1095 # for logging
1096 self._format_login(token_info)
1097 token_id = token_info["_id"]
Rahulae89d122023-12-05 11:54:38 +00001098 if current_backend != "keystone":
1099 token_details = self.engine.db.get_one("tokens", {"_id": token_id})
1100 current_user = token_details.get("username")
1101 current_project = token_details.get("project_name")
1102 else:
1103 current_user = "keystone backend"
1104 current_project = "keystone backend"
tiernoa5035702019-07-29 08:54:42 +00001105 outdata = self.authenticator.del_token(token_id)
1106 token_info = None
garciadeblasf2af4a12023-01-24 16:56:54 +01001107 cherrypy.session["Authorization"] = "logout" # pylint: disable=E1101
elumalai7802ff82023-04-24 20:38:32 +05301108 cef_event(
1109 cef_logger,
1110 {
1111 "name": "User Logout",
1112 "sourceUserName": current_user,
1113 "message": "User Logged Out, Project={} Outcome=Success".format(
1114 current_project
1115 ),
1116 },
1117 )
1118 cherrypy.log("{}".format(cef_logger))
tiernoa5035702019-07-29 08:54:42 +00001119 # cherrypy.response.cookie["Authorization"] = token_id
1120 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1121 else:
garciadeblas4568a372021-03-24 09:19:48 +01001122 raise NbiException(
1123 "Method {} not allowed for token".format(method),
1124 HTTPStatus.METHOD_NOT_ALLOWED,
1125 )
tiernoa5035702019-07-29 08:54:42 +00001126 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001127
1128 @cherrypy.expose
1129 def test(self, *args, **kwargs):
garciadeblas4568a372021-03-24 09:19:48 +01001130 if not cherrypy.config.get("server.enable_test") or (
1131 isinstance(cherrypy.config["server.enable_test"], str)
1132 and cherrypy.config["server.enable_test"].lower() == "false"
1133 ):
tierno4836bac2020-01-15 14:41:48 +00001134 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value
1135 return "test URL is disabled"
tiernoc94c3df2018-02-09 15:38:54 +01001136 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +01001137 if args and args[0] == "help":
garciadeblas4568a372021-03-24 09:19:48 +01001138 return (
1139 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1140 "sleep/<time>\nmessage/topic\n</pre></html>"
1141 )
tiernof27c79b2018-03-12 17:08:42 +01001142
1143 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +01001144 try:
1145 # self.engine.load_dbase(cherrypy.request.app.config)
garciadeblasf2af4a12023-01-24 16:56:54 +01001146 pid = self.authenticator.create_admin_project()
1147 self.authenticator.create_admin_user(pid)
tiernoc94c3df2018-02-09 15:38:54 +01001148 return "Done. User 'admin', password 'admin' created"
1149 except Exception:
1150 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1151 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +01001152 elif args and args[0] == "file":
garciadeblas4568a372021-03-24 09:19:48 +01001153 return cherrypy.lib.static.serve_file(
1154 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1],
1155 "text/plain",
1156 "attachment",
1157 )
tiernof27c79b2018-03-12 17:08:42 +01001158 elif args and args[0] == "file2":
garciadeblas4568a372021-03-24 09:19:48 +01001159 f_path = (
1160 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1]
1161 )
tiernof27c79b2018-03-12 17:08:42 +01001162 f = open(f_path, "r")
1163 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +01001164 return f
tierno55945e72018-04-06 16:40:27 +02001165
tiernof27c79b2018-03-12 17:08:42 +01001166 elif len(args) == 2 and args[0] == "db-clear":
tiernof717cbe2018-12-03 16:35:42 +00001167 deleted_info = self.engine.db.del_list(args[1], kwargs)
1168 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
1169 elif len(args) and args[0] == "fs-clear":
1170 if len(args) >= 2:
1171 folders = (args[1],)
1172 else:
1173 folders = self.engine.fs.dir_ls(".")
1174 for folder in folders:
1175 self.engine.fs.file_delete(folder)
1176 return ",".join(folders) + " folders deleted\n"
tiernoc94c3df2018-02-09 15:38:54 +01001177 elif args and args[0] == "login":
1178 if not cherrypy.request.headers.get("Authorization"):
garciadeblas4568a372021-03-24 09:19:48 +01001179 cherrypy.response.headers[
1180 "WWW-Authenticate"
1181 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
tiernoc94c3df2018-02-09 15:38:54 +01001182 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1183 elif args and args[0] == "login2":
1184 if not cherrypy.request.headers.get("Authorization"):
garciadeblas4568a372021-03-24 09:19:48 +01001185 cherrypy.response.headers[
1186 "WWW-Authenticate"
1187 ] = 'Bearer realm="Access to OSM site"'
tiernoc94c3df2018-02-09 15:38:54 +01001188 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1189 elif args and args[0] == "sleep":
1190 sleep_time = 5
1191 try:
1192 sleep_time = int(args[1])
1193 except Exception:
1194 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1195 return self._format_out("Database already initialized")
1196 thread_info = cherrypy.thread_data
1197 print(thread_info)
1198 time.sleep(sleep_time)
1199 # thread_info
1200 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +02001201 main_topic = args[1]
1202 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +01001203 try:
garciadeblas4568a372021-03-24 09:19:48 +01001204 if cherrypy.request.method == "POST":
garciadeblas4cd875d2023-02-14 19:05:34 +01001205 to_send = yaml.safe_load(cherrypy.request.body)
tierno55945e72018-04-06 16:40:27 +02001206 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +02001207 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +02001208 return_text += " {}: {}\n".format(k, v)
garciadeblas4568a372021-03-24 09:19:48 +01001209 elif cherrypy.request.method == "GET":
tierno55945e72018-04-06 16:40:27 +02001210 for k, v in kwargs.items():
garciadeblas4cd875d2023-02-14 19:05:34 +01001211 v_dict = yaml.safe_load(v)
tiernof1509b22020-05-12 14:32:37 +00001212 self.engine.msg.write(main_topic, k, v_dict)
1213 return_text += " {}: {}\n".format(k, v_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001214 except Exception as e:
tierno55945e72018-04-06 16:40:27 +02001215 return_text += "Error: " + str(e)
1216 return_text += "</pre></html>\n"
1217 return return_text
tiernoc94c3df2018-02-09 15:38:54 +01001218
1219 return_text = (
garciadeblas4568a372021-03-24 09:19:48 +01001220 "<html><pre>\nheaders:\n args: {}\n".format(args)
1221 + " kwargs: {}\n".format(kwargs)
1222 + " headers: {}\n".format(cherrypy.request.headers)
1223 + " path_info: {}\n".format(cherrypy.request.path_info)
1224 + " query_string: {}\n".format(cherrypy.request.query_string)
garciadeblasf2af4a12023-01-24 16:56:54 +01001225 + " session: {}\n".format(cherrypy.session) # pylint: disable=E1101
garciadeblas4568a372021-03-24 09:19:48 +01001226 + " cookie: {}\n".format(cherrypy.request.cookie)
1227 + " method: {}\n".format(cherrypy.request.method)
garciadeblasf2af4a12023-01-24 16:56:54 +01001228 + " session: {}\n".format(
1229 cherrypy.session.get("fieldname") # pylint: disable=E1101
1230 )
garciadeblas4568a372021-03-24 09:19:48 +01001231 + " body:\n"
1232 )
tiernoc94c3df2018-02-09 15:38:54 +01001233 return_text += " length: {}\n".format(cherrypy.request.body.length)
1234 if cherrypy.request.body.length:
1235 return_text += " content: {}\n".format(
garciadeblas4568a372021-03-24 09:19:48 +01001236 str(
1237 cherrypy.request.body.read(
1238 int(cherrypy.request.headers.get("Content-Length", 0))
1239 )
1240 )
1241 )
tiernoc94c3df2018-02-09 15:38:54 +01001242 if thread_info:
1243 return_text += "thread: {}\n".format(thread_info)
1244 return_text += "</pre></html>"
1245 return return_text
1246
tierno701018c2019-06-25 11:13:14 +00001247 @staticmethod
1248 def _check_valid_url_method(method, *args):
tiernof27c79b2018-03-12 17:08:42 +01001249 if len(args) < 3:
garciadeblas4568a372021-03-24 09:19:48 +01001250 raise NbiException(
1251 "URL must contain at least 'main_topic/version/topic'",
1252 HTTPStatus.METHOD_NOT_ALLOWED,
1253 )
tiernof27c79b2018-03-12 17:08:42 +01001254
tierno701018c2019-06-25 11:13:14 +00001255 reference = valid_url_methods
tiernof27c79b2018-03-12 17:08:42 +01001256 for arg in args:
1257 if arg is None:
1258 break
1259 if not isinstance(reference, dict):
garciadeblas4568a372021-03-24 09:19:48 +01001260 raise NbiException(
1261 "URL contains unexpected extra items '{}'".format(arg),
1262 HTTPStatus.METHOD_NOT_ALLOWED,
1263 )
tiernof27c79b2018-03-12 17:08:42 +01001264
1265 if arg in reference:
1266 reference = reference[arg]
1267 elif "<ID>" in reference:
1268 reference = reference["<ID>"]
1269 elif "*" in reference:
tierno74b53582020-06-18 10:52:37 +00001270 # if there is content
1271 if reference["*"]:
1272 reference = reference["*"]
tiernof27c79b2018-03-12 17:08:42 +01001273 break
1274 else:
garciadeblas4568a372021-03-24 09:19:48 +01001275 raise NbiException(
1276 "Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED
1277 )
tiernof27c79b2018-03-12 17:08:42 +01001278 if "TODO" in reference and method in reference["TODO"]:
garciadeblas4568a372021-03-24 09:19:48 +01001279 raise NbiException(
1280 "Method {} not supported yet for this URL".format(method),
1281 HTTPStatus.NOT_IMPLEMENTED,
1282 )
tierno2236d202018-05-16 19:05:16 +02001283 elif "METHODS" in reference and method not in reference["METHODS"]:
garciadeblas4568a372021-03-24 09:19:48 +01001284 raise NbiException(
1285 "Method {} not supported for this URL".format(method),
1286 HTTPStatus.METHOD_NOT_ALLOWED,
1287 )
tierno701018c2019-06-25 11:13:14 +00001288 return reference["ROLE_PERMISSION"] + method.lower()
tiernof27c79b2018-03-12 17:08:42 +01001289
1290 @staticmethod
tiernob24258a2018-10-04 18:39:49 +02001291 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +01001292 """
1293 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +02001294 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +01001295 :param version:
tiernob24258a2018-10-04 18:39:49 +02001296 :param topic:
tiernof27c79b2018-03-12 17:08:42 +01001297 :param id:
1298 :return: None
1299 """
1300 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
garciadeblas4568a372021-03-24 09:19:48 +01001301 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(
1302 main_topic, version, topic, id
1303 )
tiernof27c79b2018-03-12 17:08:42 +01001304 return
1305
tierno65ca36d2019-02-12 19:27:52 +01001306 @staticmethod
tierno701018c2019-06-25 11:13:14 +00001307 def _extract_query_string_operations(kwargs, method):
1308 """
1309
1310 :param kwargs:
1311 :return:
1312 """
1313 query_string_operations = []
1314 if kwargs:
1315 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1316 if qs in kwargs and kwargs[qs].lower() != "false":
1317 query_string_operations.append(qs.lower() + ":" + method.lower())
1318 return query_string_operations
1319
1320 @staticmethod
1321 def _manage_admin_query(token_info, kwargs, method, _id):
tierno65ca36d2019-02-12 19:27:52 +01001322 """
1323 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1324 Check that users has rights to use them and returs the admin_query
tierno701018c2019-06-25 11:13:14 +00001325 :param token_info: token_info rights obtained by token
tierno65ca36d2019-02-12 19:27:52 +01001326 :param kwargs: query string input.
1327 :param method: http method: GET, POSST, PUT, ...
1328 :param _id:
1329 :return: admin_query dictionary with keys:
1330 public: True, False or None
1331 force: True or False
1332 project_id: tuple with projects used for accessing an element
1333 set_project: tuple with projects that a created element will belong to
1334 method: show, list, delete, write
1335 """
garciadeblas4568a372021-03-24 09:19:48 +01001336 admin_query = {
1337 "force": False,
1338 "project_id": (token_info["project_id"],),
1339 "username": token_info["username"],
Adurti8edb1252024-05-07 06:04:37 +00001340 "user_id": token_info["user_id"],
garciadeblas4568a372021-03-24 09:19:48 +01001341 "admin": token_info["admin"],
37177c7695f62024-11-01 08:55:59 +00001342 "admin_show": token_info["admin_show"],
garciadeblas4568a372021-03-24 09:19:48 +01001343 "public": None,
1344 "allow_show_user_project_role": token_info["allow_show_user_project_role"],
1345 }
tierno65ca36d2019-02-12 19:27:52 +01001346 if kwargs:
1347 # FORCE
1348 if "FORCE" in kwargs:
garciadeblas4568a372021-03-24 09:19:48 +01001349 if (
1350 kwargs["FORCE"].lower() != "false"
1351 ): # if None or True set force to True
tierno65ca36d2019-02-12 19:27:52 +01001352 admin_query["force"] = True
1353 del kwargs["FORCE"]
1354 # PUBLIC
1355 if "PUBLIC" in kwargs:
garciadeblas4568a372021-03-24 09:19:48 +01001356 if (
1357 kwargs["PUBLIC"].lower() != "false"
1358 ): # if None or True set public to True
tierno65ca36d2019-02-12 19:27:52 +01001359 admin_query["public"] = True
1360 else:
1361 admin_query["public"] = False
1362 del kwargs["PUBLIC"]
1363 # ADMIN
1364 if "ADMIN" in kwargs:
1365 behave_as = kwargs.pop("ADMIN")
1366 if behave_as.lower() != "false":
tierno701018c2019-06-25 11:13:14 +00001367 if not token_info["admin"]:
garciadeblas4568a372021-03-24 09:19:48 +01001368 raise NbiException(
1369 "Only admin projects can use 'ADMIN' query string",
1370 HTTPStatus.UNAUTHORIZED,
1371 )
1372 if (
1373 not behave_as or behave_as.lower() == "true"
1374 ): # convert True, None to empty list
tierno65ca36d2019-02-12 19:27:52 +01001375 admin_query["project_id"] = ()
1376 elif isinstance(behave_as, (list, tuple)):
1377 admin_query["project_id"] = behave_as
garciadeblas4568a372021-03-24 09:19:48 +01001378 else: # isinstance(behave_as, str)
1379 admin_query["project_id"] = (behave_as,)
tierno65ca36d2019-02-12 19:27:52 +01001380 if "SET_PROJECT" in kwargs:
1381 set_project = kwargs.pop("SET_PROJECT")
1382 if not set_project:
1383 admin_query["set_project"] = list(admin_query["project_id"])
1384 else:
1385 if isinstance(set_project, str):
garciadeblas4568a372021-03-24 09:19:48 +01001386 set_project = (set_project,)
tierno65ca36d2019-02-12 19:27:52 +01001387 if admin_query["project_id"]:
1388 for p in set_project:
1389 if p not in admin_query["project_id"]:
garciadeblas4568a372021-03-24 09:19:48 +01001390 raise NbiException(
1391 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1392 "'ADMIN='{p}'".format(p=p),
1393 HTTPStatus.UNAUTHORIZED,
1394 )
tierno65ca36d2019-02-12 19:27:52 +01001395 admin_query["set_project"] = set_project
1396
1397 # PROJECT_READ
1398 # if "PROJECT_READ" in kwargs:
1399 # admin_query["project"] = kwargs.pop("project")
tierno701018c2019-06-25 11:13:14 +00001400 # if admin_query["project"] == token_info["project_id"]:
tierno65ca36d2019-02-12 19:27:52 +01001401 if method == "GET":
1402 if _id:
1403 admin_query["method"] = "show"
1404 else:
1405 admin_query["method"] = "list"
1406 elif method == "DELETE":
1407 admin_query["method"] = "delete"
1408 else:
1409 admin_query["method"] = "write"
1410 return admin_query
1411
tiernoc94c3df2018-02-09 15:38:54 +01001412 @cherrypy.expose
garciadeblas4568a372021-03-24 09:19:48 +01001413 def default(
1414 self,
1415 main_topic=None,
1416 version=None,
1417 topic=None,
1418 _id=None,
1419 item=None,
1420 *args,
1421 **kwargs
1422 ):
tierno701018c2019-06-25 11:13:14 +00001423 token_info = None
selvi.j9919bbc2023-04-26 12:22:13 +00001424 outdata = {}
tiernof27c79b2018-03-12 17:08:42 +01001425 _format = None
tierno0f98af52018-03-19 10:28:22 +01001426 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +02001427 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +02001428 rollback = []
tierno701018c2019-06-25 11:13:14 +00001429 engine_session = None
elumalai7802ff82023-04-24 20:38:32 +05301430 url_id = ""
1431 log_mapping = {
1432 "POST": "Creating",
1433 "GET": "Fetching",
1434 "DELETE": "Deleting",
1435 "PUT": "Updating",
1436 "PATCH": "Updating",
1437 }
tiernoc94c3df2018-02-09 15:38:54 +01001438 try:
tiernob24258a2018-10-04 18:39:49 +02001439 if not main_topic or not version or not topic:
garciadeblas4568a372021-03-24 09:19:48 +01001440 raise NbiException(
1441 "URL must contain at least 'main_topic/version/topic'",
1442 HTTPStatus.METHOD_NOT_ALLOWED,
1443 )
1444 if main_topic not in (
1445 "admin",
1446 "vnfpkgm",
1447 "nsd",
1448 "nslcm",
1449 "pdu",
1450 "nst",
1451 "nsilcm",
1452 "nspm",
almagiae47b9132022-05-17 14:12:22 +02001453 "vnflcm",
garciadeblas4568a372021-03-24 09:19:48 +01001454 ):
1455 raise NbiException(
1456 "URL main_topic '{}' not supported".format(main_topic),
1457 HTTPStatus.METHOD_NOT_ALLOWED,
1458 )
1459 if version != "v1":
1460 raise NbiException(
1461 "URL version '{}' not supported".format(version),
1462 HTTPStatus.METHOD_NOT_ALLOWED,
1463 )
elumalai7802ff82023-04-24 20:38:32 +05301464 if _id is not None:
1465 url_id = _id
tiernoc94c3df2018-02-09 15:38:54 +01001466
garciadeblas4568a372021-03-24 09:19:48 +01001467 if (
1468 kwargs
1469 and "METHOD" in kwargs
1470 and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1471 ):
tiernof27c79b2018-03-12 17:08:42 +01001472 method = kwargs.pop("METHOD")
1473 else:
1474 method = cherrypy.request.method
tierno65ca36d2019-02-12 19:27:52 +01001475
garciadeblas4568a372021-03-24 09:19:48 +01001476 role_permission = self._check_valid_url_method(
1477 method, main_topic, version, topic, _id, item, *args
1478 )
1479 query_string_operations = self._extract_query_string_operations(
1480 kwargs, method
1481 )
tiernob24258a2018-10-04 18:39:49 +02001482 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +01001483 return self.token(method, _id, kwargs)
garciadeblas4568a372021-03-24 09:19:48 +01001484 token_info = self.authenticator.authorize(
1485 role_permission, query_string_operations, _id
1486 )
tierno12eac3c2020-03-19 23:22:08 +00001487 if main_topic == "admin" and topic == "domains":
1488 return self.domain()
tierno701018c2019-06-25 11:13:14 +00001489 engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
tiernof27c79b2018-03-12 17:08:42 +01001490 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +02001491 engine_topic = topic
preethika.p329b8182020-04-22 12:25:39 +05301492
vijay.r35ef2f72019-04-30 17:55:49 +05301493 if item and topic != "pm_jobs":
tiernob24258a2018-10-04 18:39:49 +02001494 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +01001495
tiernob24258a2018-10-04 18:39:49 +02001496 if main_topic == "nsd":
1497 engine_topic = "nsds"
1498 elif main_topic == "vnfpkgm":
1499 engine_topic = "vnfds"
delacruzramo271d2002019-12-02 21:00:37 +01001500 if topic == "vnfpkg_op_occs":
1501 engine_topic = "vnfpkgops"
1502 if topic == "vnf_packages" and item == "action":
1503 engine_topic = "vnfpkgops"
tiernob24258a2018-10-04 18:39:49 +02001504 elif main_topic == "nslcm":
1505 engine_topic = "nsrs"
1506 if topic == "ns_lcm_op_occs":
1507 engine_topic = "nslcmops"
1508 if topic == "vnfrs" or topic == "vnf_instances":
1509 engine_topic = "vnfrs"
almagiae47b9132022-05-17 14:12:22 +02001510 elif main_topic == "vnflcm":
1511 if topic == "vnf_lcm_op_occs":
1512 engine_topic = "vnflcmops"
garciadeblas9750c5a2018-10-15 16:20:35 +02001513 elif main_topic == "nst":
1514 engine_topic = "nsts"
1515 elif main_topic == "nsilcm":
1516 engine_topic = "nsis"
1517 if topic == "nsi_lcm_op_occs":
delacruzramoc061f562019-04-05 11:00:02 +02001518 engine_topic = "nsilcmops"
tiernob24258a2018-10-04 18:39:49 +02001519 elif main_topic == "pdu":
1520 engine_topic = "pdus"
garciadeblas4568a372021-03-24 09:19:48 +01001521 if (
1522 engine_topic == "vims"
1523 ): # TODO this is for backward compatibility, it will be removed in the future
tiernob24258a2018-10-04 18:39:49 +02001524 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +01001525
preethika.p329b8182020-04-22 12:25:39 +05301526 if topic == "subscriptions":
1527 engine_topic = main_topic + "_" + topic
1528
tiernoc94c3df2018-02-09 15:38:54 +01001529 if method == "GET":
garciadeblas4568a372021-03-24 09:19:48 +01001530 if item in (
1531 "nsd_content",
1532 "package_content",
1533 "artifacts",
1534 "vnfd",
1535 "nsd",
1536 "nst",
1537 "nst_content",
1538 ):
garciadeblas9750c5a2018-10-15 16:20:35 +02001539 if item in ("vnfd", "nsd", "nst"):
tiernof27c79b2018-03-12 17:08:42 +01001540 path = "$DESCRIPTOR"
1541 elif args:
1542 path = args
tiernob24258a2018-10-04 18:39:49 +02001543 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +01001544 path = ()
1545 else:
1546 path = None
garciadeblas4568a372021-03-24 09:19:48 +01001547 file, _format = self.engine.get_file(
1548 engine_session,
1549 engine_topic,
1550 _id,
1551 path,
1552 cherrypy.request.headers.get("Accept"),
1553 )
tiernof27c79b2018-03-12 17:08:42 +01001554 outdata = file
1555 elif not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001556 outdata = self.engine.get_item_list(
1557 engine_session, engine_topic, kwargs, api_req=True
1558 )
tiernoc94c3df2018-02-09 15:38:54 +01001559 else:
vijay.r35ef2f72019-04-30 17:55:49 +05301560 if item == "reports":
1561 # TODO check that project_id (_id in this context) has permissions
1562 _id = args[0]
K Sai Kiran57589552021-01-27 21:38:34 +05301563 filter_q = None
1564 if "vcaStatusRefresh" in kwargs:
1565 filter_q = {"vcaStatusRefresh": kwargs["vcaStatusRefresh"]}
garciadeblasf2af4a12023-01-24 16:56:54 +01001566 outdata = self.engine.get_item(
1567 engine_session, engine_topic, _id, filter_q, True
1568 )
delacruzramo271d2002019-12-02 21:00:37 +01001569
tiernof27c79b2018-03-12 17:08:42 +01001570 elif method == "POST":
tiernobdebce92019-07-01 15:36:49 +00001571 cherrypy.response.status = HTTPStatus.CREATED.value
garciadeblas4568a372021-03-24 09:19:48 +01001572 if topic in (
1573 "ns_descriptors_content",
1574 "vnf_packages_content",
1575 "netslice_templates_content",
1576 ):
tiernof27c79b2018-03-12 17:08:42 +01001577 _id = cherrypy.request.headers.get("Transaction-Id")
1578 if not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001579 _id, _ = self.engine.new_item(
1580 rollback,
1581 engine_session,
1582 engine_topic,
1583 {},
1584 None,
1585 cherrypy.request.headers,
1586 )
1587 completed = self.engine.upload_content(
1588 engine_session,
1589 engine_topic,
1590 _id,
1591 indata,
1592 kwargs,
1593 cherrypy.request.headers,
1594 )
tiernof27c79b2018-03-12 17:08:42 +01001595 if completed:
tiernob24258a2018-10-04 18:39:49 +02001596 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001597 else:
1598 cherrypy.response.headers["Transaction-Id"] = _id
1599 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +02001600 elif topic == "ns_instances_content":
1601 # creates NSR
garciadeblas4568a372021-03-24 09:19:48 +01001602 _id, _ = self.engine.new_item(
1603 rollback, engine_session, engine_topic, indata, kwargs
1604 )
tiernob24258a2018-10-04 18:39:49 +02001605 # creates nslcmop
1606 indata["lcmOperationType"] = "instantiate"
1607 indata["nsInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001608 nslcmop_id, _ = self.engine.new_item(
1609 rollback, engine_session, "nslcmops", indata, None
1610 )
tiernob24258a2018-10-04 18:39:49 +02001611 self._set_location_header(main_topic, version, topic, _id)
kuuse078f55e2019-05-16 19:24:21 +02001612 outdata = {"id": _id, "nslcmop_id": nslcmop_id}
tiernob24258a2018-10-04 18:39:49 +02001613 elif topic == "ns_instances" and item:
1614 indata["lcmOperationType"] = item
1615 indata["nsInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001616 _id, _ = self.engine.new_item(
1617 rollback, engine_session, "nslcmops", indata, kwargs
1618 )
1619 self._set_location_header(
1620 main_topic, version, "ns_lcm_op_occs", _id
1621 )
tierno65acb4d2018-04-06 16:42:40 +02001622 outdata = {"id": _id}
1623 cherrypy.response.status = HTTPStatus.ACCEPTED.value
garciadeblas9750c5a2018-10-15 16:20:35 +02001624 elif topic == "netslice_instances_content":
Felipe Vicens07f31722018-10-29 15:16:44 +01001625 # creates NetSlice_Instance_record (NSIR)
garciadeblas4568a372021-03-24 09:19:48 +01001626 _id, _ = self.engine.new_item(
1627 rollback, engine_session, engine_topic, indata, kwargs
1628 )
Felipe Vicens07f31722018-10-29 15:16:44 +01001629 self._set_location_header(main_topic, version, topic, _id)
garciadeblas9750c5a2018-10-15 16:20:35 +02001630 indata["lcmOperationType"] = "instantiate"
Felipe Vicens126af572019-06-05 19:13:04 +02001631 indata["netsliceInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001632 nsilcmop_id, _ = self.engine.new_item(
1633 rollback, engine_session, "nsilcmops", indata, kwargs
1634 )
kuuse078f55e2019-05-16 19:24:21 +02001635 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
garciadeblas9750c5a2018-10-15 16:20:35 +02001636 elif topic == "netslice_instances" and item:
1637 indata["lcmOperationType"] = item
Felipe Vicens126af572019-06-05 19:13:04 +02001638 indata["netsliceInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001639 _id, _ = self.engine.new_item(
1640 rollback, engine_session, "nsilcmops", indata, kwargs
1641 )
1642 self._set_location_header(
1643 main_topic, version, "nsi_lcm_op_occs", _id
1644 )
garciadeblas9750c5a2018-10-15 16:20:35 +02001645 outdata = {"id": _id}
1646 cherrypy.response.status = HTTPStatus.ACCEPTED.value
delacruzramo271d2002019-12-02 21:00:37 +01001647 elif topic == "vnf_packages" and item == "action":
1648 indata["lcmOperationType"] = item
1649 indata["vnfPkgId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001650 _id, _ = self.engine.new_item(
1651 rollback, engine_session, "vnfpkgops", indata, kwargs
1652 )
1653 self._set_location_header(
1654 main_topic, version, "vnfpkg_op_occs", _id
1655 )
delacruzramo271d2002019-12-02 21:00:37 +01001656 outdata = {"id": _id}
1657 cherrypy.response.status = HTTPStatus.ACCEPTED.value
preethika.p329b8182020-04-22 12:25:39 +05301658 elif topic == "subscriptions":
garciadeblas4568a372021-03-24 09:19:48 +01001659 _id, _ = self.engine.new_item(
1660 rollback, engine_session, engine_topic, indata, kwargs
1661 )
preethika.p329b8182020-04-22 12:25:39 +05301662 self._set_location_header(main_topic, version, topic, _id)
1663 link = {}
1664 link["self"] = cherrypy.response.headers["Location"]
garciadeblas4568a372021-03-24 09:19:48 +01001665 outdata = {
1666 "id": _id,
1667 "filter": indata["filter"],
1668 "callbackUri": indata["CallbackUri"],
1669 "_links": link,
1670 }
preethika.p329b8182020-04-22 12:25:39 +05301671 cherrypy.response.status = HTTPStatus.CREATED.value
almagiae47b9132022-05-17 14:12:22 +02001672 elif topic == "vnf_instances" and item:
1673 indata["lcmOperationType"] = item
1674 indata["vnfInstanceId"] = _id
garciadeblasf2af4a12023-01-24 16:56:54 +01001675 _id, _ = self.engine.new_item(
1676 rollback, engine_session, "vnflcmops", indata, kwargs
1677 )
1678 self._set_location_header(
1679 main_topic, version, "vnf_lcm_op_occs", _id
1680 )
almagiae47b9132022-05-17 14:12:22 +02001681 outdata = {"id": _id}
1682 cherrypy.response.status = HTTPStatus.ACCEPTED.value
Gabriel Cuba84a60df2023-10-30 14:01:54 -05001683 elif topic == "ns_lcm_op_occs" and item == "cancel":
1684 indata["nsLcmOpOccId"] = _id
1685 self.engine.cancel_item(
1686 rollback, engine_session, "nslcmops", indata, None
1687 )
1688 self._set_location_header(main_topic, version, topic, _id)
1689 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +01001690 else:
garciadeblas4568a372021-03-24 09:19:48 +01001691 _id, op_id = self.engine.new_item(
1692 rollback,
1693 engine_session,
1694 engine_topic,
1695 indata,
1696 kwargs,
1697 cherrypy.request.headers,
1698 )
tiernob24258a2018-10-04 18:39:49 +02001699 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001700 outdata = {"id": _id}
tiernobdebce92019-07-01 15:36:49 +00001701 if op_id:
1702 outdata["op_id"] = op_id
1703 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernob24258a2018-10-04 18:39:49 +02001704 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tierno09c073e2018-04-26 13:36:48 +02001705
tiernoc94c3df2018-02-09 15:38:54 +01001706 elif method == "DELETE":
1707 if not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001708 outdata = self.engine.del_item_list(
1709 engine_session, engine_topic, kwargs
1710 )
tierno09c073e2018-04-26 13:36:48 +02001711 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +01001712 else: # len(args) > 1
tierno22577432020-04-08 15:16:57 +00001713 # for NS NSI generate an operation
1714 op_id = None
tierno701018c2019-06-25 11:13:14 +00001715 if topic == "ns_instances_content" and not engine_session["force"]:
tiernob24258a2018-10-04 18:39:49 +02001716 nslcmop_desc = {
1717 "lcmOperationType": "terminate",
1718 "nsInstanceId": _id,
garciadeblas4568a372021-03-24 09:19:48 +01001719 "autoremove": True,
tiernob24258a2018-10-04 18:39:49 +02001720 }
garciadeblas4568a372021-03-24 09:19:48 +01001721 op_id, _ = self.engine.new_item(
1722 rollback, engine_session, "nslcmops", nslcmop_desc, kwargs
1723 )
tierno22577432020-04-08 15:16:57 +00001724 if op_id:
1725 outdata = {"_id": op_id}
garciadeblas4568a372021-03-24 09:19:48 +01001726 elif (
1727 topic == "netslice_instances_content"
1728 and not engine_session["force"]
1729 ):
garciadeblas9750c5a2018-10-15 16:20:35 +02001730 nsilcmop_desc = {
1731 "lcmOperationType": "terminate",
Felipe Vicens126af572019-06-05 19:13:04 +02001732 "netsliceInstanceId": _id,
garciadeblas4568a372021-03-24 09:19:48 +01001733 "autoremove": True,
garciadeblas9750c5a2018-10-15 16:20:35 +02001734 }
garciadeblas4568a372021-03-24 09:19:48 +01001735 op_id, _ = self.engine.new_item(
1736 rollback, engine_session, "nsilcmops", nsilcmop_desc, None
1737 )
tierno22577432020-04-08 15:16:57 +00001738 if op_id:
1739 outdata = {"_id": op_id}
1740 # if there is not any deletion in process, delete
1741 if not op_id:
1742 op_id = self.engine.del_item(engine_session, engine_topic, _id)
1743 if op_id:
1744 outdata = {"op_id": op_id}
garciadeblas4568a372021-03-24 09:19:48 +01001745 cherrypy.response.status = (
1746 HTTPStatus.ACCEPTED.value
1747 if op_id
1748 else HTTPStatus.NO_CONTENT.value
1749 )
tierno09c073e2018-04-26 13:36:48 +02001750
tierno7ae10112018-05-18 14:36:02 +02001751 elif method in ("PUT", "PATCH"):
tiernobdebce92019-07-01 15:36:49 +00001752 op_id = None
tierno701018c2019-06-25 11:13:14 +00001753 if not indata and not kwargs and not engine_session.get("set_project"):
garciadeblas4568a372021-03-24 09:19:48 +01001754 raise NbiException(
1755 "Nothing to update. Provide payload and/or query string",
1756 HTTPStatus.BAD_REQUEST,
1757 )
1758 if (
1759 item in ("nsd_content", "package_content", "nst_content")
1760 and method == "PUT"
1761 ):
1762 completed = self.engine.upload_content(
1763 engine_session,
1764 engine_topic,
1765 _id,
1766 indata,
1767 kwargs,
1768 cherrypy.request.headers,
1769 )
tiernof27c79b2018-03-12 17:08:42 +01001770 if not completed:
1771 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +01001772 else:
garciadeblas4568a372021-03-24 09:19:48 +01001773 op_id = self.engine.edit_item(
1774 engine_session, engine_topic, _id, indata, kwargs
1775 )
tiernobdebce92019-07-01 15:36:49 +00001776
1777 if op_id:
1778 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1779 outdata = {"op_id": op_id}
1780 else:
1781 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
1782 outdata = None
tiernoc94c3df2018-02-09 15:38:54 +01001783 else:
garciadeblas4568a372021-03-24 09:19:48 +01001784 raise NbiException(
1785 "Method {} not allowed".format(method),
1786 HTTPStatus.METHOD_NOT_ALLOWED,
1787 )
tiernoa6bb45d2019-06-14 09:45:39 +00001788
1789 # if Role information changes, it is needed to reload the information of roles
1790 if topic == "roles" and method != "GET":
1791 self.authenticator.load_operation_to_allowed_roles()
delacruzramoad682a52019-12-10 16:26:34 +01001792
garciadeblas4568a372021-03-24 09:19:48 +01001793 if (
1794 topic == "projects"
1795 and method == "DELETE"
1796 or topic in ["users", "roles"]
1797 and method in ["PUT", "PATCH", "DELETE"]
1798 ):
delacruzramoad682a52019-12-10 16:26:34 +01001799 self.authenticator.remove_token_from_cache()
1800
elumalai7802ff82023-04-24 20:38:32 +05301801 if item is not None:
1802 cef_event(
1803 cef_logger,
1804 {
1805 "name": "User Operation",
1806 "sourceUserName": token_info.get("username"),
1807 "message": "Performing {} operation on {} {}, Project={} Outcome=Success".format(
1808 item,
1809 topic,
1810 url_id,
1811 token_info.get("project_name"),
1812 ),
1813 },
1814 )
1815 cherrypy.log("{}".format(cef_logger))
1816 else:
1817 cef_event(
1818 cef_logger,
1819 {
1820 "name": "User Operation",
1821 "sourceUserName": token_info.get("username"),
1822 "message": "{} {} {}, Project={} Outcome=Success".format(
1823 log_mapping[method],
1824 topic,
1825 url_id,
1826 token_info.get("project_name"),
1827 ),
1828 },
1829 )
1830 cherrypy.log("{}".format(cef_logger))
tierno701018c2019-06-25 11:13:14 +00001831 return self._format_out(outdata, token_info, _format)
tiernob24258a2018-10-04 18:39:49 +02001832 except Exception as e:
garciadeblas4568a372021-03-24 09:19:48 +01001833 if isinstance(
1834 e,
1835 (
1836 NbiException,
1837 EngineException,
1838 DbException,
1839 FsException,
1840 MsgException,
1841 AuthException,
1842 ValidationError,
1843 AuthconnException,
1844 ),
1845 ):
tiernob24258a2018-10-04 18:39:49 +02001846 http_code_value = cherrypy.response.status = e.http_code.value
1847 http_code_name = e.http_code.name
1848 cherrypy.log("Exception {}".format(e))
1849 else:
garciadeblas4568a372021-03-24 09:19:48 +01001850 http_code_value = (
1851 cherrypy.response.status
1852 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Felipe Vicense36ab852018-11-23 14:12:09 +01001853 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
tiernob24258a2018-10-04 18:39:49 +02001854 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +02001855 if hasattr(outdata, "close"): # is an open file
1856 outdata.close()
tiernob24258a2018-10-04 18:39:49 +02001857 error_text = str(e)
1858 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +02001859 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +02001860 try:
tiernocc103432018-10-19 14:10:35 +02001861 if rollback_item.get("operation") == "set":
garciadeblas4568a372021-03-24 09:19:48 +01001862 self.engine.db.set_one(
1863 rollback_item["topic"],
1864 {"_id": rollback_item["_id"]},
1865 rollback_item["content"],
1866 fail_on_empty=False,
1867 )
preethika.p329b8182020-04-22 12:25:39 +05301868 elif rollback_item.get("operation") == "del_list":
garciadeblas4568a372021-03-24 09:19:48 +01001869 self.engine.db.del_list(
1870 rollback_item["topic"],
1871 rollback_item["filter"],
garciadeblas4568a372021-03-24 09:19:48 +01001872 )
tiernocc103432018-10-19 14:10:35 +02001873 else:
garciadeblas4568a372021-03-24 09:19:48 +01001874 self.engine.db.del_one(
1875 rollback_item["topic"],
1876 {"_id": rollback_item["_id"]},
1877 fail_on_empty=False,
1878 )
tierno3ace63c2018-05-03 17:51:43 +02001879 except Exception as e2:
garciadeblas4568a372021-03-24 09:19:48 +01001880 rollback_error_text = "Rollback Exception {}: {}".format(
1881 rollback_item, e2
1882 )
tiernob24258a2018-10-04 18:39:49 +02001883 cherrypy.log(rollback_error_text)
1884 error_text += ". " + rollback_error_text
1885 # if isinstance(e, MsgException):
1886 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1887 # engine_topic[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +01001888 problem_details = {
tiernob24258a2018-10-04 18:39:49 +02001889 "code": http_code_name,
1890 "status": http_code_value,
1891 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +01001892 }
elumalai7802ff82023-04-24 20:38:32 +05301893 if item is not None and token_info is not None:
1894 cef_event(
1895 cef_logger,
1896 {
1897 "name": "User Operation",
1898 "sourceUserName": token_info.get("username", None),
1899 "message": "Performing {} operation on {} {}, Project={} Outcome=Failure".format(
1900 item,
1901 topic,
1902 url_id,
1903 token_info.get("project_name", None),
1904 ),
1905 "severity": "2",
1906 },
1907 )
1908 cherrypy.log("{}".format(cef_logger))
1909 elif token_info is not None:
1910 cef_event(
1911 cef_logger,
1912 {
1913 "name": "User Operation",
1914 "sourceUserName": token_info.get("username", None),
1915 "message": "{} {} {}, Project={} Outcome=Failure".format(
1916 item,
1917 topic,
1918 url_id,
1919 token_info.get("project_name", None),
1920 ),
1921 "severity": "2",
1922 },
1923 )
1924 cherrypy.log("{}".format(cef_logger))
tierno701018c2019-06-25 11:13:14 +00001925 return self._format_out(problem_details, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001926 # raise cherrypy.HTTPError(e.http_code.value, str(e))
tiernoa5035702019-07-29 08:54:42 +00001927 finally:
1928 if token_info:
1929 self._format_login(token_info)
1930 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
1931 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1932 if outdata.get(logging_id):
garciadeblas4568a372021-03-24 09:19:48 +01001933 cherrypy.request.login += ";{}={}".format(
1934 logging_id, outdata[logging_id][:36]
1935 )
tiernoc94c3df2018-02-09 15:38:54 +01001936
1937
tiernoc94c3df2018-02-09 15:38:54 +01001938def _start_service():
1939 """
1940 Callback function called when cherrypy.engine starts
1941 Override configuration with env variables
1942 Set database, storage, message configuration
1943 Init database with admin/admin user password
1944 """
tierno932499c2019-01-28 17:28:10 +00001945 global nbi_server
1946 global subscription_thread
elumalai7802ff82023-04-24 20:38:32 +05301947 global cef_logger
Rahulae89d122023-12-05 11:54:38 +00001948 global current_backend
tiernoc94c3df2018-02-09 15:38:54 +01001949 cherrypy.log.error("Starting osm_nbi")
1950 # update general cherrypy configuration
1951 update_dict = {}
1952
garciadeblas4568a372021-03-24 09:19:48 +01001953 engine_config = cherrypy.tree.apps["/osm"].config
tiernoc94c3df2018-02-09 15:38:54 +01001954 for k, v in environ.items():
garciadeblas6d83f8f2023-06-19 22:34:49 +02001955 if k == "OSMNBI_USER_MANAGEMENT":
1956 feature_state = eval(v.title())
1957 engine_config["authentication"]["user_management"] = feature_state
tiernoc94c3df2018-02-09 15:38:54 +01001958 if not k.startswith("OSMNBI_"):
1959 continue
tiernoe1281182018-05-22 12:24:36 +02001960 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +01001961 if not k2:
1962 continue
1963 try:
1964 # update static configuration
garciadeblas4568a372021-03-24 09:19:48 +01001965 if k == "OSMNBI_STATIC_DIR":
1966 engine_config["/static"]["tools.staticdir.dir"] = v
1967 engine_config["/static"]["tools.staticdir.on"] = True
1968 elif k == "OSMNBI_SOCKET_PORT" or k == "OSMNBI_SERVER_PORT":
1969 update_dict["server.socket_port"] = int(v)
1970 elif k == "OSMNBI_SOCKET_HOST" or k == "OSMNBI_SERVER_HOST":
1971 update_dict["server.socket_host"] = v
tiernof5298be2018-05-16 14:43:57 +02001972 elif k1 in ("server", "test", "auth", "log"):
garciadeblas4568a372021-03-24 09:19:48 +01001973 update_dict[k1 + "." + k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001974 elif k1 in ("message", "database", "storage", "authentication"):
tiernof5298be2018-05-16 14:43:57 +02001975 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001976 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +01001977 engine_config[k1][k2] = int(v)
1978 else:
1979 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001980
tiernoc94c3df2018-02-09 15:38:54 +01001981 except ValueError as e:
1982 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1983 except Exception as e:
garciadeblasf2af4a12023-01-24 16:56:54 +01001984 cherrypy.log(
1985 "WARNING: skipping environ '{}' on exception '{}'".format(k, e)
1986 )
tiernoc94c3df2018-02-09 15:38:54 +01001987
1988 if update_dict:
1989 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +02001990 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001991
1992 # logging cherrypy
garciadeblas4568a372021-03-24 09:19:48 +01001993 log_format_simple = (
1994 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1995 )
1996 log_formatter_simple = logging.Formatter(
1997 log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S"
1998 )
tiernoc94c3df2018-02-09 15:38:54 +01001999 logger_server = logging.getLogger("cherrypy.error")
2000 logger_access = logging.getLogger("cherrypy.access")
2001 logger_cherry = logging.getLogger("cherrypy")
2002 logger_nbi = logging.getLogger("nbi")
2003
tiernof5298be2018-05-16 14:43:57 +02002004 if "log.file" in engine_config["global"]:
garciadeblas4568a372021-03-24 09:19:48 +01002005 file_handler = logging.handlers.RotatingFileHandler(
2006 engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0
2007 )
tiernoc94c3df2018-02-09 15:38:54 +01002008 file_handler.setFormatter(log_formatter_simple)
2009 logger_cherry.addHandler(file_handler)
2010 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +02002011 # log always to standard output
garciadeblas4568a372021-03-24 09:19:48 +01002012 for format_, logger in {
2013 "nbi.server %(filename)s:%(lineno)s": logger_server,
2014 "nbi.access %(filename)s:%(lineno)s": logger_access,
2015 "%(name)s %(filename)s:%(lineno)s": logger_nbi,
2016 }.items():
tiernob24258a2018-10-04 18:39:49 +02002017 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
garciadeblas4568a372021-03-24 09:19:48 +01002018 log_formatter_cherry = logging.Formatter(
2019 log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S"
2020 )
tiernob24258a2018-10-04 18:39:49 +02002021 str_handler = logging.StreamHandler()
2022 str_handler.setFormatter(log_formatter_cherry)
2023 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +01002024
tiernof5298be2018-05-16 14:43:57 +02002025 if engine_config["global"].get("log.level"):
2026 logger_cherry.setLevel(engine_config["global"]["log.level"])
2027 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +01002028
2029 # logging other modules
garciadeblas4568a372021-03-24 09:19:48 +01002030 for k1, logname in {
2031 "message": "nbi.msg",
2032 "database": "nbi.db",
2033 "storage": "nbi.fs",
2034 }.items():
tiernoc94c3df2018-02-09 15:38:54 +01002035 engine_config[k1]["logger_name"] = logname
2036 logger_module = logging.getLogger(logname)
2037 if "logfile" in engine_config[k1]:
garciadeblas4568a372021-03-24 09:19:48 +01002038 file_handler = logging.handlers.RotatingFileHandler(
2039 engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0
2040 )
tiernoc94c3df2018-02-09 15:38:54 +01002041 file_handler.setFormatter(log_formatter_simple)
2042 logger_module.addHandler(file_handler)
2043 if "loglevel" in engine_config[k1]:
2044 logger_module.setLevel(engine_config[k1]["loglevel"])
2045 # TODO add more entries, e.g.: storage
garciadeblas4568a372021-03-24 09:19:48 +01002046 cherrypy.tree.apps["/osm"].root.engine.start(engine_config)
2047 cherrypy.tree.apps["/osm"].root.authenticator.start(engine_config)
2048 cherrypy.tree.apps["/osm"].root.engine.init_db(target_version=database_version)
2049 cherrypy.tree.apps["/osm"].root.authenticator.init_db(
2050 target_version=auth_database_version
2051 )
tiernobee508e2019-01-21 11:21:49 +00002052
elumalai7802ff82023-04-24 20:38:32 +05302053 cef_logger = cef_event_builder(engine_config["authentication"])
2054
tierno932499c2019-01-28 17:28:10 +00002055 # start subscriptions thread:
garciadeblas4568a372021-03-24 09:19:48 +01002056 subscription_thread = SubscriptionThread(
2057 config=engine_config, engine=nbi_server.engine
2058 )
tierno932499c2019-01-28 17:28:10 +00002059 subscription_thread.start()
2060 # Do not capture except SubscriptionException
2061
tiernob2e48bd2020-02-04 15:47:18 +00002062 backend = engine_config["authentication"]["backend"]
Rahulae89d122023-12-05 11:54:38 +00002063 current_backend = backend
garciadeblas4568a372021-03-24 09:19:48 +01002064 cherrypy.log.error(
2065 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
2066 nbi_version, nbi_version_date, backend
2067 )
2068 )
tiernoc94c3df2018-02-09 15:38:54 +01002069
2070
2071def _stop_service():
2072 """
2073 Callback function called when cherrypy.engine stops
2074 TODO: Ending database connections.
2075 """
tierno932499c2019-01-28 17:28:10 +00002076 global subscription_thread
tierno65ca36d2019-02-12 19:27:52 +01002077 if subscription_thread:
2078 subscription_thread.terminate()
tierno932499c2019-01-28 17:28:10 +00002079 subscription_thread = None
garciadeblas4568a372021-03-24 09:19:48 +01002080 cherrypy.tree.apps["/osm"].root.engine.stop()
tiernoc94c3df2018-02-09 15:38:54 +01002081 cherrypy.log.error("Stopping osm_nbi")
2082
tierno2236d202018-05-16 19:05:16 +02002083
tiernof5298be2018-05-16 14:43:57 +02002084def nbi(config_file):
tierno932499c2019-01-28 17:28:10 +00002085 global nbi_server
tiernoc94c3df2018-02-09 15:38:54 +01002086 # conf = {
2087 # '/': {
2088 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
2089 # 'tools.sessions.on': True,
2090 # 'tools.response_headers.on': True,
2091 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
2092 # }
2093 # }
2094 # cherrypy.Server.ssl_module = 'builtin'
2095 # cherrypy.Server.ssl_certificate = "http/cert.pem"
2096 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
2097 # cherrypy.Server.thread_pool = 10
2098 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
2099
2100 # cherrypy.config.update({'tools.auth_basic.on': True,
2101 # 'tools.auth_basic.realm': 'localhost',
2102 # 'tools.auth_basic.checkpassword': validate_password})
tierno932499c2019-01-28 17:28:10 +00002103 nbi_server = Server()
garciadeblas4568a372021-03-24 09:19:48 +01002104 cherrypy.engine.subscribe("start", _start_service)
2105 cherrypy.engine.subscribe("stop", _stop_service)
2106 cherrypy.quickstart(nbi_server, "/osm", config_file)
tiernof5298be2018-05-16 14:43:57 +02002107
2108
2109def usage():
garciadeblas4568a372021-03-24 09:19:48 +01002110 print(
2111 """Usage: {} [options]
tiernof5298be2018-05-16 14:43:57 +02002112 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
2113 -h|--help: shows this help
garciadeblas4568a372021-03-24 09:19:48 +01002114 """.format(
2115 sys.argv[0]
2116 )
2117 )
tierno2236d202018-05-16 19:05:16 +02002118 # --log-socket-host HOST: send logs to this host")
2119 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +01002120
2121
garciadeblas4568a372021-03-24 09:19:48 +01002122if __name__ == "__main__":
tiernof5298be2018-05-16 14:43:57 +02002123 try:
2124 # load parameters and configuration
2125 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
2126 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
2127 config_file = None
2128 for o, a in opts:
2129 if o in ("-h", "--help"):
2130 usage()
2131 sys.exit()
2132 elif o in ("-c", "--config"):
2133 config_file = a
2134 # elif o == "--log-socket-port":
2135 # log_socket_port = a
2136 # elif o == "--log-socket-host":
2137 # log_socket_host = a
2138 # elif o == "--log-file":
2139 # log_file = a
2140 else:
2141 assert False, "Unhandled option"
2142 if config_file:
2143 if not path.isfile(config_file):
garciadeblas4568a372021-03-24 09:19:48 +01002144 print(
2145 "configuration file '{}' that not exist".format(config_file),
2146 file=sys.stderr,
2147 )
tiernof5298be2018-05-16 14:43:57 +02002148 exit(1)
2149 else:
garciadeblas4568a372021-03-24 09:19:48 +01002150 for config_file in (
2151 __file__[: __file__.rfind(".")] + ".cfg",
2152 "./nbi.cfg",
2153 "/etc/osm/nbi.cfg",
2154 ):
tiernof5298be2018-05-16 14:43:57 +02002155 if path.isfile(config_file):
2156 break
2157 else:
garciadeblas4568a372021-03-24 09:19:48 +01002158 print(
2159 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
2160 file=sys.stderr,
2161 )
tiernof5298be2018-05-16 14:43:57 +02002162 exit(1)
2163 nbi(config_file)
2164 except getopt.GetoptError as e:
2165 print(str(e), file=sys.stderr)
2166 # usage()
2167 exit(1)