blob: e4b7368bbe36684a9727e08073326ce5e348e21e [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 },
garciadeblas4568a372021-03-24 09:19:48 +0100466 },
467 },
468 "ns_lcm_op_occs": {
469 "METHODS": ("GET",),
470 "ROLE_PERMISSION": "ns_instances:opps:",
471 "<ID>": {
472 "METHODS": ("GET",),
473 "ROLE_PERMISSION": "ns_instances:opps:id:",
Gabriel Cuba84a60df2023-10-30 14:01:54 -0500474 "cancel": {
475 "METHODS": ("POST",),
476 "ROLE_PERMISSION": "ns_instances:opps:cancel:",
477 },
garciadeblas4568a372021-03-24 09:19:48 +0100478 },
479 },
480 "vnfrs": {
481 "METHODS": ("GET",),
482 "ROLE_PERMISSION": "vnf_instances:",
483 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
484 },
485 "vnf_instances": {
486 "METHODS": ("GET",),
487 "ROLE_PERMISSION": "vnf_instances:",
488 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
489 },
490 "subscriptions": {
491 "METHODS": ("GET", "POST"),
492 "ROLE_PERMISSION": "ns_subscriptions:",
493 "<ID>": {
494 "METHODS": ("GET", "DELETE"),
495 "ROLE_PERMISSION": "ns_subscriptions:id:",
496 },
497 },
tierno701018c2019-06-25 11:13:14 +0000498 }
499 },
almagiae47b9132022-05-17 14:12:22 +0200500 "vnflcm": {
501 "v1": {
garciadeblasf2af4a12023-01-24 16:56:54 +0100502 "vnf_instances": {
503 "METHODS": ("GET", "POST"),
504 "ROLE_PERMISSION": "vnflcm_instances:",
505 "<ID>": {
506 "METHODS": ("GET", "DELETE"),
507 "ROLE_PERMISSION": "vnflcm_instances:id:",
508 "scale": {
509 "METHODS": ("POST",),
510 "ROLE_PERMISSION": "vnflcm_instances:id:scale:",
511 },
512 "terminate": {
513 "METHODS": ("POST",),
514 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:",
515 },
516 "instantiate": {
517 "METHODS": ("POST",),
518 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:",
519 },
520 },
521 },
522 "vnf_lcm_op_occs": {
523 "METHODS": ("GET",),
524 "ROLE_PERMISSION": "vnf_instances:opps:",
525 "<ID>": {
526 "METHODS": ("GET",),
527 "ROLE_PERMISSION": "vnf_instances:opps:id:",
528 },
529 },
530 "subscriptions": {
531 "METHODS": ("GET", "POST"),
532 "ROLE_PERMISSION": "vnflcm_subscriptions:",
533 "<ID>": {
534 "METHODS": ("GET", "DELETE"),
535 "ROLE_PERMISSION": "vnflcm_subscriptions:id:",
536 },
537 },
almagiae47b9132022-05-17 14:12:22 +0200538 }
539 },
tierno701018c2019-06-25 11:13:14 +0000540 "nst": {
541 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100542 "netslice_templates_content": {
543 "METHODS": ("GET", "POST"),
544 "ROLE_PERMISSION": "slice_templates:",
545 "<ID>": {
546 "METHODS": ("GET", "PUT", "DELETE"),
547 "ROLE_PERMISSION": "slice_templates:id:",
548 },
549 },
550 "netslice_templates": {
551 "METHODS": ("GET", "POST"),
552 "ROLE_PERMISSION": "slice_templates:",
553 "<ID>": {
554 "METHODS": ("GET", "DELETE"),
555 "TODO": ("PATCH",),
556 "ROLE_PERMISSION": "slice_templates:id:",
557 "nst_content": {
558 "METHODS": ("GET", "PUT"),
559 "ROLE_PERMISSION": "slice_templates:id:content:",
560 },
561 "nst": {
562 "METHODS": ("GET",), # descriptor inside package
563 "ROLE_PERMISSION": "slice_templates:id:content:",
564 },
565 "artifacts": {
566 "METHODS": ("GET",),
567 "ROLE_PERMISSION": "slice_templates:id:content:",
568 "*": None,
569 },
570 },
571 },
572 "subscriptions": {
573 "TODO": ("GET", "POST"),
574 "<ID>": {"TODO": ("GET", "DELETE")},
575 },
tierno701018c2019-06-25 11:13:14 +0000576 }
577 },
578 "nsilcm": {
579 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100580 "netslice_instances_content": {
581 "METHODS": ("GET", "POST"),
582 "ROLE_PERMISSION": "slice_instances:",
583 "<ID>": {
584 "METHODS": ("GET", "DELETE"),
585 "ROLE_PERMISSION": "slice_instances:id:",
586 },
587 },
588 "netslice_instances": {
589 "METHODS": ("GET", "POST"),
590 "ROLE_PERMISSION": "slice_instances:",
591 "<ID>": {
592 "METHODS": ("GET", "DELETE"),
593 "ROLE_PERMISSION": "slice_instances:id:",
594 "terminate": {
595 "METHODS": ("POST",),
596 "ROLE_PERMISSION": "slice_instances:id:terminate:",
597 },
598 "instantiate": {
599 "METHODS": ("POST",),
600 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
601 },
602 "action": {
603 "METHODS": ("POST",),
604 "ROLE_PERMISSION": "slice_instances:id:action:",
605 },
606 },
607 },
608 "nsi_lcm_op_occs": {
609 "METHODS": ("GET",),
610 "ROLE_PERMISSION": "slice_instances:opps:",
611 "<ID>": {
612 "METHODS": ("GET",),
613 "ROLE_PERMISSION": "slice_instances:opps:id:",
614 },
615 },
tierno701018c2019-06-25 11:13:14 +0000616 }
617 },
618 "nspm": {
619 "v1": {
620 "pm_jobs": {
621 "<ID>": {
622 "reports": {
garciadeblas4568a372021-03-24 09:19:48 +0100623 "<ID>": {
624 "METHODS": ("GET",),
625 "ROLE_PERMISSION": "reports:id:",
626 }
tierno701018c2019-06-25 11:13:14 +0000627 }
628 },
629 },
630 },
631 },
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000632 "nsfm": {
633 "v1": {
garciadeblasf2af4a12023-01-24 16:56:54 +0100634 "alarms": {
635 "METHODS": ("GET", "PATCH"),
636 "ROLE_PERMISSION": "alarms:",
637 "<ID>": {
638 "METHODS": ("GET", "PATCH"),
639 "ROLE_PERMISSION": "alarms:id:",
640 },
641 }
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000642 },
643 },
tierno701018c2019-06-25 11:13:14 +0000644}
645
tiernoc94c3df2018-02-09 15:38:54 +0100646
647class NbiException(Exception):
tiernoc94c3df2018-02-09 15:38:54 +0100648 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
649 Exception.__init__(self, message)
650 self.http_code = http_code
651
652
653class Server(object):
654 instance = 0
655 # to decode bytes to str
656 reader = getreader("utf-8")
657
658 def __init__(self):
659 self.instance += 1
tierno701018c2019-06-25 11:13:14 +0000660 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
delacruzramoad682a52019-12-10 16:26:34 +0100661 self.engine = Engine(self.authenticator)
tiernoc94c3df2018-02-09 15:38:54 +0100662
tiernoc94c3df2018-02-09 15:38:54 +0100663 def _format_in(self, kwargs):
garciadeblasf2af4a12023-01-24 16:56:54 +0100664 error_text = "" # error_text must be initialized outside try
tiernoc94c3df2018-02-09 15:38:54 +0100665 try:
666 indata = None
667 if cherrypy.request.body.length:
668 error_text = "Invalid input format "
669
670 if "Content-Type" in cherrypy.request.headers:
671 if "application/json" in cherrypy.request.headers["Content-Type"]:
672 error_text = "Invalid json format "
673 indata = json.load(self.reader(cherrypy.request.body))
gcalvinode4adfe2018-10-30 11:46:09 +0100674 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100675 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
676 error_text = "Invalid yaml format "
garciadeblas4cd875d2023-02-14 19:05:34 +0100677 indata = yaml.safe_load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100678 cherrypy.request.headers.pop("Content-File-MD5", None)
garciadeblas4568a372021-03-24 09:19:48 +0100679 elif (
680 "application/binary" in cherrypy.request.headers["Content-Type"]
681 or "application/gzip"
682 in cherrypy.request.headers["Content-Type"]
683 or "application/zip" in cherrypy.request.headers["Content-Type"]
684 or "text/plain" in cherrypy.request.headers["Content-Type"]
685 ):
tiernof27c79b2018-03-12 17:08:42 +0100686 indata = cherrypy.request.body # .read()
garciadeblas4568a372021-03-24 09:19:48 +0100687 elif (
688 "multipart/form-data"
689 in cherrypy.request.headers["Content-Type"]
690 ):
tiernoc94c3df2018-02-09 15:38:54 +0100691 if "descriptor_file" in kwargs:
692 filecontent = kwargs.pop("descriptor_file")
693 if not filecontent.file:
garciadeblas4568a372021-03-24 09:19:48 +0100694 raise NbiException(
695 "empty file or content", HTTPStatus.BAD_REQUEST
696 )
tiernof27c79b2018-03-12 17:08:42 +0100697 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100698 if filecontent.content_type.value:
garciadeblas4568a372021-03-24 09:19:48 +0100699 cherrypy.request.headers[
700 "Content-Type"
701 ] = filecontent.content_type.value
tiernoc94c3df2018-02-09 15:38:54 +0100702 else:
703 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
704 # "Only 'Content-Type' of type 'application/json' or
705 # 'application/yaml' for input format are available")
706 error_text = "Invalid yaml format "
garciadeblas4cd875d2023-02-14 19:05:34 +0100707 indata = yaml.safe_load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100708 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100709 else:
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 if not indata:
714 indata = {}
715
tiernoc94c3df2018-02-09 15:38:54 +0100716 format_yaml = False
717 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
718 format_yaml = True
719
720 for k, v in kwargs.items():
721 if isinstance(v, str):
722 if v == "":
723 kwargs[k] = None
724 elif format_yaml:
725 try:
garciadeblas4cd875d2023-02-14 19:05:34 +0100726 kwargs[k] = yaml.safe_load(v)
tiernoe1281182018-05-22 12:24:36 +0200727 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100728 pass
garciadeblas4568a372021-03-24 09:19:48 +0100729 elif (
730 k.endswith(".gt")
731 or k.endswith(".lt")
732 or k.endswith(".gte")
733 or k.endswith(".lte")
734 ):
tiernoc94c3df2018-02-09 15:38:54 +0100735 try:
736 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200737 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100738 try:
739 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200740 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100741 pass
742 elif v.find(",") > 0:
743 kwargs[k] = v.split(",")
744 elif isinstance(v, (list, tuple)):
745 for index in range(0, len(v)):
746 if v[index] == "":
747 v[index] = None
748 elif format_yaml:
749 try:
garciadeblas4cd875d2023-02-14 19:05:34 +0100750 v[index] = yaml.safe_load(v[index])
tiernoe1281182018-05-22 12:24:36 +0200751 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100752 pass
753
tiernof27c79b2018-03-12 17:08:42 +0100754 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100755 except (ValueError, yaml.YAMLError) as exc:
756 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
757 except KeyError as exc:
garciadeblas4568a372021-03-24 09:19:48 +0100758 raise NbiException(
759 "Query string error: " + str(exc), HTTPStatus.BAD_REQUEST
760 )
tiernob92094f2018-05-11 13:44:22 +0200761 except Exception as exc:
762 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100763
764 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000765 def _format_out(data, token_info=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100766 """
767 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100768 :param data: response to be sent. Can be a dict, text or file
tierno701018c2019-06-25 11:13:14 +0000769 :param token_info: Contains among other username and project
tierno5792d7d2019-08-30 15:37:12 +0000770 :param _format: The format to be set as Content-Type if data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100771 :return: None
772 """
tierno0f98af52018-03-19 10:28:22 +0100773 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100774 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100775 if accept and "text/html" in accept:
garciadeblas4568a372021-03-24 09:19:48 +0100776 return html.format(
777 data, cherrypy.request, cherrypy.response, token_info
778 )
tierno09c073e2018-04-26 13:36:48 +0200779 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100780 return
781 elif hasattr(data, "read"): # file object
782 if _format:
783 cherrypy.response.headers["Content-Type"] = _format
784 elif "b" in data.mode: # binariy asssumig zip
garciadeblas4568a372021-03-24 09:19:48 +0100785 cherrypy.response.headers["Content-Type"] = "application/zip"
tiernof27c79b2018-03-12 17:08:42 +0100786 else:
garciadeblas4568a372021-03-24 09:19:48 +0100787 cherrypy.response.headers["Content-Type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100788 # TODO check that cherrypy close file. If not implement pending things to close per thread next
789 return data
tierno0f98af52018-03-19 10:28:22 +0100790 if accept:
Frank Bryden02e700c2020-06-03 13:34:16 +0000791 if "text/html" in accept:
garciadeblas4568a372021-03-24 09:19:48 +0100792 return html.format(
793 data, cherrypy.request, cherrypy.response, token_info
794 )
Frank Brydenb5422da2020-08-10 11:44:11 +0000795 elif "application/yaml" in accept or "*/*" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100796 pass
garciadeblas4568a372021-03-24 09:19:48 +0100797 elif "application/json" in accept or (
798 cherrypy.response.status and cherrypy.response.status >= 300
799 ):
800 cherrypy.response.headers[
801 "Content-Type"
802 ] = "application/json; charset=utf-8"
Frank Bryden02e700c2020-06-03 13:34:16 +0000803 a = json.dumps(data, indent=4) + "\n"
garciadeblas4568a372021-03-24 09:19:48 +0100804 return a.encode("utf8")
805 cherrypy.response.headers["Content-Type"] = "application/yaml"
806 return yaml.safe_dump(
807 data,
808 explicit_start=True,
809 indent=4,
810 default_flow_style=False,
811 tags=False,
812 encoding="utf-8",
813 allow_unicode=True,
814 ) # , canonical=True, default_style='"'
tiernoc94c3df2018-02-09 15:38:54 +0100815
816 @cherrypy.expose
817 def index(self, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000818 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100819 try:
820 if cherrypy.request.method == "GET":
tierno701018c2019-06-25 11:13:14 +0000821 token_info = self.authenticator.authorize()
garciadeblas4568a372021-03-24 09:19:48 +0100822 outdata = token_info # Home page
tiernoc94c3df2018-02-09 15:38:54 +0100823 else:
garciadeblas4568a372021-03-24 09:19:48 +0100824 raise cherrypy.HTTPError(
825 HTTPStatus.METHOD_NOT_ALLOWED.value,
826 "Method {} not allowed for tokens".format(cherrypy.request.method),
827 )
tiernoc94c3df2018-02-09 15:38:54 +0100828
tierno701018c2019-06-25 11:13:14 +0000829 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100830
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100831 except (EngineException, AuthException) as e:
tierno701018c2019-06-25 11:13:14 +0000832 # cherrypy.log("index Exception {}".format(e))
tiernoc94c3df2018-02-09 15:38:54 +0100833 cherrypy.response.status = e.http_code.value
tierno701018c2019-06-25 11:13:14 +0000834 return self._format_out("Welcome to OSM!", token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100835
836 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200837 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200838 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200839 try:
840 if cherrypy.request.method != "GET":
garciadeblas4568a372021-03-24 09:19:48 +0100841 raise NbiException(
842 "Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED
843 )
tierno55945e72018-04-06 16:40:27 +0200844 elif args or kwargs:
garciadeblas4568a372021-03-24 09:19:48 +0100845 raise NbiException(
846 "Invalid URL or query string for version",
847 HTTPStatus.METHOD_NOT_ALLOWED,
848 )
tierno9c630112019-08-29 14:21:41 +0000849 # TODO include version of other modules, pick up from some kafka admin message
tierno5792d7d2019-08-30 15:37:12 +0000850 osm_nbi_version = {"version": nbi_version, "date": nbi_version_date}
851 return self._format_out(osm_nbi_version)
tierno55945e72018-04-06 16:40:27 +0200852 except NbiException as e:
853 cherrypy.response.status = e.http_code.value
854 problem_details = {
855 "code": e.http_code.name,
856 "status": e.http_code.value,
857 "detail": str(e),
858 }
859 return self._format_out(problem_details, None)
860
tierno12eac3c2020-03-19 23:22:08 +0000861 def domain(self):
862 try:
863 domains = {
garciadeblas4568a372021-03-24 09:19:48 +0100864 "user_domain_name": cherrypy.tree.apps["/osm"]
865 .config["authentication"]
866 .get("user_domain_name"),
867 "project_domain_name": cherrypy.tree.apps["/osm"]
868 .config["authentication"]
869 .get("project_domain_name"),
870 }
tierno12eac3c2020-03-19 23:22:08 +0000871 return self._format_out(domains)
872 except NbiException as e:
873 cherrypy.response.status = e.http_code.value
874 problem_details = {
875 "code": e.http_code.name,
876 "status": e.http_code.value,
877 "detail": str(e),
878 }
879 return self._format_out(problem_details, None)
880
tiernoa5035702019-07-29 08:54:42 +0000881 @staticmethod
882 def _format_login(token_info):
883 """
884 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
885 log this information
886 :param token_info: Dictionary with token content
887 :return: None
888 """
889 cherrypy.request.login = token_info.get("username", "-")
890 if token_info.get("project_name"):
891 cherrypy.request.login += "/" + token_info["project_name"]
892 if token_info.get("id"):
893 cherrypy.request.login += ";session=" + token_info["id"][0:12]
894
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000895 # NS Fault Management
896 @cherrypy.expose
garciadeblasf2af4a12023-01-24 16:56:54 +0100897 def nsfm(
898 self,
899 version=None,
900 topic=None,
901 uuid=None,
902 project_name=None,
903 ns_id=None,
904 *args,
905 **kwargs
906 ):
907 if topic == "alarms":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000908 try:
909 method = cherrypy.request.method
garciadeblasf2af4a12023-01-24 16:56:54 +0100910 role_permission = self._check_valid_url_method(
911 method, "nsfm", version, topic, None, None, *args
912 )
913 query_string_operations = self._extract_query_string_operations(
914 kwargs, method
915 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000916
garciadeblasf2af4a12023-01-24 16:56:54 +0100917 self.authenticator.authorize(
918 role_permission, query_string_operations, None
919 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000920
921 # to handle get request
garciadeblasf2af4a12023-01-24 16:56:54 +0100922 if cherrypy.request.method == "GET":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000923 # if request is on basis of uuid
garciadeblasf2af4a12023-01-24 16:56:54 +0100924 if uuid and uuid != "None":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000925 try:
926 alarm = self.engine.db.get_one("alarms", {"uuid": uuid})
garciadeblasf2af4a12023-01-24 16:56:54 +0100927 alarm_action = self.engine.db.get_one(
928 "alarms_action", {"uuid": uuid}
929 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000930 alarm.update(alarm_action)
garciadeblasf2af4a12023-01-24 16:56:54 +0100931 vnf = self.engine.db.get_one(
932 "vnfrs", {"nsr-id-ref": alarm["tags"]["ns_id"]}
933 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000934 alarm["vnf-id"] = vnf["_id"]
935 return self._format_out(str(alarm))
936 except Exception:
937 return self._format_out("Please provide valid alarm uuid")
garciadeblasf2af4a12023-01-24 16:56:54 +0100938 elif ns_id and ns_id != "None":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000939 # if request is on basis of ns_id
940 try:
garciadeblasf2af4a12023-01-24 16:56:54 +0100941 alarms = self.engine.db.get_list(
942 "alarms", {"tags.ns_id": ns_id}
943 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000944 for alarm in alarms:
garciadeblasf2af4a12023-01-24 16:56:54 +0100945 alarm_action = self.engine.db.get_one(
946 "alarms_action", {"uuid": alarm["uuid"]}
947 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000948 alarm.update(alarm_action)
949 return self._format_out(str(alarms))
950 except Exception:
951 return self._format_out("Please provide valid ns id")
952 else:
953 # to return only alarm which are related to given project
garciadeblasf2af4a12023-01-24 16:56:54 +0100954 project = self.engine.db.get_one(
955 "projects", {"name": project_name}
956 )
957 project_id = project.get("_id")
958 ns_list = self.engine.db.get_list(
959 "nsrs", {"_admin.projects_read": project_id}
960 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000961 ns_ids = []
962 for ns in ns_list:
963 ns_ids.append(ns.get("_id"))
964 alarms = self.engine.db.get_list("alarms")
garciadeblasf2af4a12023-01-24 16:56:54 +0100965 alarm_list = [
966 alarm
967 for alarm in alarms
968 if alarm["tags"]["ns_id"] in ns_ids
969 ]
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000970 for alrm in alarm_list:
garciadeblasf2af4a12023-01-24 16:56:54 +0100971 action = self.engine.db.get_one(
972 "alarms_action", {"uuid": alrm.get("uuid")}
973 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000974 alrm.update(action)
975 return self._format_out(str(alarm_list))
976 # to handle patch request for alarm update
garciadeblasf2af4a12023-01-24 16:56:54 +0100977 elif cherrypy.request.method == "PATCH":
garciadeblas4cd875d2023-02-14 19:05:34 +0100978 data = yaml.safe_load(cherrypy.request.body)
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000979 try:
980 # check if uuid is valid
981 self.engine.db.get_one("alarms", {"uuid": data.get("uuid")})
982 except Exception:
983 return self._format_out("Please provide valid alarm uuid.")
984 if data.get("is_enable") is not None:
985 if data.get("is_enable"):
garciadeblasf2af4a12023-01-24 16:56:54 +0100986 alarm_status = "ok"
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000987 else:
garciadeblasf2af4a12023-01-24 16:56:54 +0100988 alarm_status = "disabled"
989 self.engine.db.set_one(
990 "alarms",
991 {"uuid": data.get("uuid")},
992 {"alarm_status": alarm_status},
993 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000994 else:
garciadeblasf2af4a12023-01-24 16:56:54 +0100995 self.engine.db.set_one(
996 "alarms",
997 {"uuid": data.get("uuid")},
998 {"threshold": data.get("threshold")},
999 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001000 return self._format_out("Alarm updated")
1001 except Exception as e:
garciadeblasf2af4a12023-01-24 16:56:54 +01001002 if isinstance(
1003 e,
1004 (
1005 NbiException,
1006 EngineException,
1007 DbException,
1008 FsException,
1009 MsgException,
1010 AuthException,
1011 ValidationError,
1012 AuthconnException,
1013 ),
1014 ):
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001015 http_code_value = cherrypy.response.status = e.http_code.value
1016 http_code_name = e.http_code.name
1017 cherrypy.log("Exception {}".format(e))
1018 else:
garciadeblasf2af4a12023-01-24 16:56:54 +01001019 http_code_value = (
1020 cherrypy.response.status
1021 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001022 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
1023 http_code_name = HTTPStatus.BAD_REQUEST.name
1024 problem_details = {
1025 "code": http_code_name,
1026 "status": http_code_value,
1027 "detail": str(e),
1028 }
1029 return self._format_out(problem_details)
1030
tierno55945e72018-04-06 16:40:27 +02001031 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +01001032 def token(self, method, token_id=None, kwargs=None):
tierno701018c2019-06-25 11:13:14 +00001033 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +01001034 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +01001035 indata = self._format_in(kwargs)
1036 if not isinstance(indata, dict):
garciadeblas4568a372021-03-24 09:19:48 +01001037 raise NbiException(
1038 "Expected application/yaml or application/json Content-Type",
1039 HTTPStatus.BAD_REQUEST,
1040 )
tiernoa5035702019-07-29 08:54:42 +00001041
1042 if method == "GET":
1043 token_info = self.authenticator.authorize()
1044 # for logging
1045 self._format_login(token_info)
1046 if token_id:
1047 outdata = self.authenticator.get_token(token_info, token_id)
tiernoc94c3df2018-02-09 15:38:54 +01001048 else:
tiernoa5035702019-07-29 08:54:42 +00001049 outdata = self.authenticator.get_token_list(token_info)
1050 elif method == "POST":
1051 try:
1052 token_info = self.authenticator.authorize()
1053 except Exception:
1054 token_info = None
1055 if kwargs:
1056 indata.update(kwargs)
1057 # This is needed to log the user when authentication fails
1058 cherrypy.request.login = "{}".format(indata.get("username", "-"))
garciadeblas4568a372021-03-24 09:19:48 +01001059 outdata = token_info = self.authenticator.new_token(
1060 token_info, indata, cherrypy.request.remote
1061 )
garciadeblasf2af4a12023-01-24 16:56:54 +01001062 cherrypy.session["Authorization"] = outdata["_id"] # pylint: disable=E1101
tiernoa5035702019-07-29 08:54:42 +00001063 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
1064 # for logging
1065 self._format_login(token_info)
selvi.ja9a1fc82022-04-04 06:54:30 +00001066 # password expiry check
1067 if self.authenticator.check_password_expiry(outdata):
garciadeblasf2af4a12023-01-24 16:56:54 +01001068 outdata = {
1069 "id": outdata["id"],
1070 "message": "change_password",
1071 "user_id": outdata["user_id"],
1072 }
tiernoa5035702019-07-29 08:54:42 +00001073 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1074 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
elumalai7802ff82023-04-24 20:38:32 +05301075 cef_event(
1076 cef_logger,
1077 {
1078 "name": "User Login",
1079 "sourceUserName": token_info.get("username"),
1080 "message": "User Logged In, Project={} Outcome=Success".format(
1081 token_info.get("project_name")
1082 ),
1083 },
1084 )
1085 cherrypy.log("{}".format(cef_logger))
tiernoa5035702019-07-29 08:54:42 +00001086 elif method == "DELETE":
1087 if not token_id and "id" in kwargs:
1088 token_id = kwargs["id"]
1089 elif not token_id:
1090 token_info = self.authenticator.authorize()
1091 # for logging
1092 self._format_login(token_info)
1093 token_id = token_info["_id"]
Rahulc72bc8e2023-12-05 11:54:38 +00001094 if current_backend != "keystone":
1095 token_details = self.engine.db.get_one("tokens", {"_id": token_id})
1096 current_user = token_details.get("username")
1097 current_project = token_details.get("project_name")
1098 else:
1099 current_user = "keystone backend"
1100 current_project = "keystone backend"
tiernoa5035702019-07-29 08:54:42 +00001101 outdata = self.authenticator.del_token(token_id)
1102 token_info = None
garciadeblasf2af4a12023-01-24 16:56:54 +01001103 cherrypy.session["Authorization"] = "logout" # pylint: disable=E1101
elumalai7802ff82023-04-24 20:38:32 +05301104 cef_event(
1105 cef_logger,
1106 {
1107 "name": "User Logout",
1108 "sourceUserName": current_user,
1109 "message": "User Logged Out, Project={} Outcome=Success".format(
1110 current_project
1111 ),
1112 },
1113 )
1114 cherrypy.log("{}".format(cef_logger))
tiernoa5035702019-07-29 08:54:42 +00001115 # cherrypy.response.cookie["Authorization"] = token_id
1116 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1117 else:
garciadeblas4568a372021-03-24 09:19:48 +01001118 raise NbiException(
1119 "Method {} not allowed for token".format(method),
1120 HTTPStatus.METHOD_NOT_ALLOWED,
1121 )
tiernoa5035702019-07-29 08:54:42 +00001122 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001123
1124 @cherrypy.expose
1125 def test(self, *args, **kwargs):
garciadeblas4568a372021-03-24 09:19:48 +01001126 if not cherrypy.config.get("server.enable_test") or (
1127 isinstance(cherrypy.config["server.enable_test"], str)
1128 and cherrypy.config["server.enable_test"].lower() == "false"
1129 ):
tierno4836bac2020-01-15 14:41:48 +00001130 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value
1131 return "test URL is disabled"
tiernoc94c3df2018-02-09 15:38:54 +01001132 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +01001133 if args and args[0] == "help":
garciadeblas4568a372021-03-24 09:19:48 +01001134 return (
1135 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1136 "sleep/<time>\nmessage/topic\n</pre></html>"
1137 )
tiernof27c79b2018-03-12 17:08:42 +01001138
1139 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +01001140 try:
1141 # self.engine.load_dbase(cherrypy.request.app.config)
garciadeblasf2af4a12023-01-24 16:56:54 +01001142 pid = self.authenticator.create_admin_project()
1143 self.authenticator.create_admin_user(pid)
tiernoc94c3df2018-02-09 15:38:54 +01001144 return "Done. User 'admin', password 'admin' created"
1145 except Exception:
1146 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1147 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +01001148 elif args and args[0] == "file":
garciadeblas4568a372021-03-24 09:19:48 +01001149 return cherrypy.lib.static.serve_file(
1150 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1],
1151 "text/plain",
1152 "attachment",
1153 )
tiernof27c79b2018-03-12 17:08:42 +01001154 elif args and args[0] == "file2":
garciadeblas4568a372021-03-24 09:19:48 +01001155 f_path = (
1156 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1]
1157 )
tiernof27c79b2018-03-12 17:08:42 +01001158 f = open(f_path, "r")
1159 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +01001160 return f
tierno55945e72018-04-06 16:40:27 +02001161
tiernof27c79b2018-03-12 17:08:42 +01001162 elif len(args) == 2 and args[0] == "db-clear":
tiernof717cbe2018-12-03 16:35:42 +00001163 deleted_info = self.engine.db.del_list(args[1], kwargs)
1164 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
1165 elif len(args) and args[0] == "fs-clear":
1166 if len(args) >= 2:
1167 folders = (args[1],)
1168 else:
1169 folders = self.engine.fs.dir_ls(".")
1170 for folder in folders:
1171 self.engine.fs.file_delete(folder)
1172 return ",".join(folders) + " folders deleted\n"
tiernoc94c3df2018-02-09 15:38:54 +01001173 elif args and args[0] == "login":
1174 if not cherrypy.request.headers.get("Authorization"):
garciadeblas4568a372021-03-24 09:19:48 +01001175 cherrypy.response.headers[
1176 "WWW-Authenticate"
1177 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
tiernoc94c3df2018-02-09 15:38:54 +01001178 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1179 elif args and args[0] == "login2":
1180 if not cherrypy.request.headers.get("Authorization"):
garciadeblas4568a372021-03-24 09:19:48 +01001181 cherrypy.response.headers[
1182 "WWW-Authenticate"
1183 ] = 'Bearer realm="Access to OSM site"'
tiernoc94c3df2018-02-09 15:38:54 +01001184 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1185 elif args and args[0] == "sleep":
1186 sleep_time = 5
1187 try:
1188 sleep_time = int(args[1])
1189 except Exception:
1190 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1191 return self._format_out("Database already initialized")
1192 thread_info = cherrypy.thread_data
1193 print(thread_info)
1194 time.sleep(sleep_time)
1195 # thread_info
1196 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +02001197 main_topic = args[1]
1198 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +01001199 try:
garciadeblas4568a372021-03-24 09:19:48 +01001200 if cherrypy.request.method == "POST":
garciadeblas4cd875d2023-02-14 19:05:34 +01001201 to_send = yaml.safe_load(cherrypy.request.body)
tierno55945e72018-04-06 16:40:27 +02001202 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +02001203 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +02001204 return_text += " {}: {}\n".format(k, v)
garciadeblas4568a372021-03-24 09:19:48 +01001205 elif cherrypy.request.method == "GET":
tierno55945e72018-04-06 16:40:27 +02001206 for k, v in kwargs.items():
garciadeblas4cd875d2023-02-14 19:05:34 +01001207 v_dict = yaml.safe_load(v)
tiernof1509b22020-05-12 14:32:37 +00001208 self.engine.msg.write(main_topic, k, v_dict)
1209 return_text += " {}: {}\n".format(k, v_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001210 except Exception as e:
tierno55945e72018-04-06 16:40:27 +02001211 return_text += "Error: " + str(e)
1212 return_text += "</pre></html>\n"
1213 return return_text
tiernoc94c3df2018-02-09 15:38:54 +01001214
1215 return_text = (
garciadeblas4568a372021-03-24 09:19:48 +01001216 "<html><pre>\nheaders:\n args: {}\n".format(args)
1217 + " kwargs: {}\n".format(kwargs)
1218 + " headers: {}\n".format(cherrypy.request.headers)
1219 + " path_info: {}\n".format(cherrypy.request.path_info)
1220 + " query_string: {}\n".format(cherrypy.request.query_string)
garciadeblasf2af4a12023-01-24 16:56:54 +01001221 + " session: {}\n".format(cherrypy.session) # pylint: disable=E1101
garciadeblas4568a372021-03-24 09:19:48 +01001222 + " cookie: {}\n".format(cherrypy.request.cookie)
1223 + " method: {}\n".format(cherrypy.request.method)
garciadeblasf2af4a12023-01-24 16:56:54 +01001224 + " session: {}\n".format(
1225 cherrypy.session.get("fieldname") # pylint: disable=E1101
1226 )
garciadeblas4568a372021-03-24 09:19:48 +01001227 + " body:\n"
1228 )
tiernoc94c3df2018-02-09 15:38:54 +01001229 return_text += " length: {}\n".format(cherrypy.request.body.length)
1230 if cherrypy.request.body.length:
1231 return_text += " content: {}\n".format(
garciadeblas4568a372021-03-24 09:19:48 +01001232 str(
1233 cherrypy.request.body.read(
1234 int(cherrypy.request.headers.get("Content-Length", 0))
1235 )
1236 )
1237 )
tiernoc94c3df2018-02-09 15:38:54 +01001238 if thread_info:
1239 return_text += "thread: {}\n".format(thread_info)
1240 return_text += "</pre></html>"
1241 return return_text
1242
tierno701018c2019-06-25 11:13:14 +00001243 @staticmethod
1244 def _check_valid_url_method(method, *args):
tiernof27c79b2018-03-12 17:08:42 +01001245 if len(args) < 3:
garciadeblas4568a372021-03-24 09:19:48 +01001246 raise NbiException(
1247 "URL must contain at least 'main_topic/version/topic'",
1248 HTTPStatus.METHOD_NOT_ALLOWED,
1249 )
tiernof27c79b2018-03-12 17:08:42 +01001250
tierno701018c2019-06-25 11:13:14 +00001251 reference = valid_url_methods
tiernof27c79b2018-03-12 17:08:42 +01001252 for arg in args:
1253 if arg is None:
1254 break
1255 if not isinstance(reference, dict):
garciadeblas4568a372021-03-24 09:19:48 +01001256 raise NbiException(
1257 "URL contains unexpected extra items '{}'".format(arg),
1258 HTTPStatus.METHOD_NOT_ALLOWED,
1259 )
tiernof27c79b2018-03-12 17:08:42 +01001260
1261 if arg in reference:
1262 reference = reference[arg]
1263 elif "<ID>" in reference:
1264 reference = reference["<ID>"]
1265 elif "*" in reference:
tierno74b53582020-06-18 10:52:37 +00001266 # if there is content
1267 if reference["*"]:
1268 reference = reference["*"]
tiernof27c79b2018-03-12 17:08:42 +01001269 break
1270 else:
garciadeblas4568a372021-03-24 09:19:48 +01001271 raise NbiException(
1272 "Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED
1273 )
tiernof27c79b2018-03-12 17:08:42 +01001274 if "TODO" in reference and method in reference["TODO"]:
garciadeblas4568a372021-03-24 09:19:48 +01001275 raise NbiException(
1276 "Method {} not supported yet for this URL".format(method),
1277 HTTPStatus.NOT_IMPLEMENTED,
1278 )
tierno2236d202018-05-16 19:05:16 +02001279 elif "METHODS" in reference and method not in reference["METHODS"]:
garciadeblas4568a372021-03-24 09:19:48 +01001280 raise NbiException(
1281 "Method {} not supported for this URL".format(method),
1282 HTTPStatus.METHOD_NOT_ALLOWED,
1283 )
tierno701018c2019-06-25 11:13:14 +00001284 return reference["ROLE_PERMISSION"] + method.lower()
tiernof27c79b2018-03-12 17:08:42 +01001285
1286 @staticmethod
tiernob24258a2018-10-04 18:39:49 +02001287 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +01001288 """
1289 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +02001290 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +01001291 :param version:
tiernob24258a2018-10-04 18:39:49 +02001292 :param topic:
tiernof27c79b2018-03-12 17:08:42 +01001293 :param id:
1294 :return: None
1295 """
1296 # 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 +01001297 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(
1298 main_topic, version, topic, id
1299 )
tiernof27c79b2018-03-12 17:08:42 +01001300 return
1301
tierno65ca36d2019-02-12 19:27:52 +01001302 @staticmethod
tierno701018c2019-06-25 11:13:14 +00001303 def _extract_query_string_operations(kwargs, method):
1304 """
1305
1306 :param kwargs:
1307 :return:
1308 """
1309 query_string_operations = []
1310 if kwargs:
1311 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1312 if qs in kwargs and kwargs[qs].lower() != "false":
1313 query_string_operations.append(qs.lower() + ":" + method.lower())
1314 return query_string_operations
1315
1316 @staticmethod
1317 def _manage_admin_query(token_info, kwargs, method, _id):
tierno65ca36d2019-02-12 19:27:52 +01001318 """
1319 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1320 Check that users has rights to use them and returs the admin_query
tierno701018c2019-06-25 11:13:14 +00001321 :param token_info: token_info rights obtained by token
tierno65ca36d2019-02-12 19:27:52 +01001322 :param kwargs: query string input.
1323 :param method: http method: GET, POSST, PUT, ...
1324 :param _id:
1325 :return: admin_query dictionary with keys:
1326 public: True, False or None
1327 force: True or False
1328 project_id: tuple with projects used for accessing an element
1329 set_project: tuple with projects that a created element will belong to
1330 method: show, list, delete, write
1331 """
garciadeblas4568a372021-03-24 09:19:48 +01001332 admin_query = {
1333 "force": False,
1334 "project_id": (token_info["project_id"],),
1335 "username": token_info["username"],
1336 "admin": token_info["admin"],
1337 "public": None,
1338 "allow_show_user_project_role": token_info["allow_show_user_project_role"],
1339 }
tierno65ca36d2019-02-12 19:27:52 +01001340 if kwargs:
1341 # FORCE
1342 if "FORCE" in kwargs:
garciadeblas4568a372021-03-24 09:19:48 +01001343 if (
1344 kwargs["FORCE"].lower() != "false"
1345 ): # if None or True set force to True
tierno65ca36d2019-02-12 19:27:52 +01001346 admin_query["force"] = True
1347 del kwargs["FORCE"]
1348 # PUBLIC
1349 if "PUBLIC" in kwargs:
garciadeblas4568a372021-03-24 09:19:48 +01001350 if (
1351 kwargs["PUBLIC"].lower() != "false"
1352 ): # if None or True set public to True
tierno65ca36d2019-02-12 19:27:52 +01001353 admin_query["public"] = True
1354 else:
1355 admin_query["public"] = False
1356 del kwargs["PUBLIC"]
1357 # ADMIN
1358 if "ADMIN" in kwargs:
1359 behave_as = kwargs.pop("ADMIN")
1360 if behave_as.lower() != "false":
tierno701018c2019-06-25 11:13:14 +00001361 if not token_info["admin"]:
garciadeblas4568a372021-03-24 09:19:48 +01001362 raise NbiException(
1363 "Only admin projects can use 'ADMIN' query string",
1364 HTTPStatus.UNAUTHORIZED,
1365 )
1366 if (
1367 not behave_as or behave_as.lower() == "true"
1368 ): # convert True, None to empty list
tierno65ca36d2019-02-12 19:27:52 +01001369 admin_query["project_id"] = ()
1370 elif isinstance(behave_as, (list, tuple)):
1371 admin_query["project_id"] = behave_as
garciadeblas4568a372021-03-24 09:19:48 +01001372 else: # isinstance(behave_as, str)
1373 admin_query["project_id"] = (behave_as,)
tierno65ca36d2019-02-12 19:27:52 +01001374 if "SET_PROJECT" in kwargs:
1375 set_project = kwargs.pop("SET_PROJECT")
1376 if not set_project:
1377 admin_query["set_project"] = list(admin_query["project_id"])
1378 else:
1379 if isinstance(set_project, str):
garciadeblas4568a372021-03-24 09:19:48 +01001380 set_project = (set_project,)
tierno65ca36d2019-02-12 19:27:52 +01001381 if admin_query["project_id"]:
1382 for p in set_project:
1383 if p not in admin_query["project_id"]:
garciadeblas4568a372021-03-24 09:19:48 +01001384 raise NbiException(
1385 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1386 "'ADMIN='{p}'".format(p=p),
1387 HTTPStatus.UNAUTHORIZED,
1388 )
tierno65ca36d2019-02-12 19:27:52 +01001389 admin_query["set_project"] = set_project
1390
1391 # PROJECT_READ
1392 # if "PROJECT_READ" in kwargs:
1393 # admin_query["project"] = kwargs.pop("project")
tierno701018c2019-06-25 11:13:14 +00001394 # if admin_query["project"] == token_info["project_id"]:
tierno65ca36d2019-02-12 19:27:52 +01001395 if method == "GET":
1396 if _id:
1397 admin_query["method"] = "show"
1398 else:
1399 admin_query["method"] = "list"
1400 elif method == "DELETE":
1401 admin_query["method"] = "delete"
1402 else:
1403 admin_query["method"] = "write"
1404 return admin_query
1405
tiernoc94c3df2018-02-09 15:38:54 +01001406 @cherrypy.expose
garciadeblas4568a372021-03-24 09:19:48 +01001407 def default(
1408 self,
1409 main_topic=None,
1410 version=None,
1411 topic=None,
1412 _id=None,
1413 item=None,
1414 *args,
1415 **kwargs
1416 ):
tierno701018c2019-06-25 11:13:14 +00001417 token_info = None
selvi.j9919bbc2023-04-26 12:22:13 +00001418 outdata = {}
tiernof27c79b2018-03-12 17:08:42 +01001419 _format = None
tierno0f98af52018-03-19 10:28:22 +01001420 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +02001421 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +02001422 rollback = []
tierno701018c2019-06-25 11:13:14 +00001423 engine_session = None
elumalai7802ff82023-04-24 20:38:32 +05301424 url_id = ""
1425 log_mapping = {
1426 "POST": "Creating",
1427 "GET": "Fetching",
1428 "DELETE": "Deleting",
1429 "PUT": "Updating",
1430 "PATCH": "Updating",
1431 }
tiernoc94c3df2018-02-09 15:38:54 +01001432 try:
tiernob24258a2018-10-04 18:39:49 +02001433 if not main_topic or not version or not topic:
garciadeblas4568a372021-03-24 09:19:48 +01001434 raise NbiException(
1435 "URL must contain at least 'main_topic/version/topic'",
1436 HTTPStatus.METHOD_NOT_ALLOWED,
1437 )
1438 if main_topic not in (
1439 "admin",
1440 "vnfpkgm",
1441 "nsd",
1442 "nslcm",
1443 "pdu",
1444 "nst",
1445 "nsilcm",
1446 "nspm",
almagiae47b9132022-05-17 14:12:22 +02001447 "vnflcm",
garciadeblas4568a372021-03-24 09:19:48 +01001448 ):
1449 raise NbiException(
1450 "URL main_topic '{}' not supported".format(main_topic),
1451 HTTPStatus.METHOD_NOT_ALLOWED,
1452 )
1453 if version != "v1":
1454 raise NbiException(
1455 "URL version '{}' not supported".format(version),
1456 HTTPStatus.METHOD_NOT_ALLOWED,
1457 )
elumalai7802ff82023-04-24 20:38:32 +05301458 if _id is not None:
1459 url_id = _id
tiernoc94c3df2018-02-09 15:38:54 +01001460
garciadeblas4568a372021-03-24 09:19:48 +01001461 if (
1462 kwargs
1463 and "METHOD" in kwargs
1464 and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1465 ):
tiernof27c79b2018-03-12 17:08:42 +01001466 method = kwargs.pop("METHOD")
1467 else:
1468 method = cherrypy.request.method
tierno65ca36d2019-02-12 19:27:52 +01001469
garciadeblas4568a372021-03-24 09:19:48 +01001470 role_permission = self._check_valid_url_method(
1471 method, main_topic, version, topic, _id, item, *args
1472 )
1473 query_string_operations = self._extract_query_string_operations(
1474 kwargs, method
1475 )
tiernob24258a2018-10-04 18:39:49 +02001476 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +01001477 return self.token(method, _id, kwargs)
garciadeblas4568a372021-03-24 09:19:48 +01001478 token_info = self.authenticator.authorize(
1479 role_permission, query_string_operations, _id
1480 )
tierno12eac3c2020-03-19 23:22:08 +00001481 if main_topic == "admin" and topic == "domains":
1482 return self.domain()
tierno701018c2019-06-25 11:13:14 +00001483 engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
tiernof27c79b2018-03-12 17:08:42 +01001484 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +02001485 engine_topic = topic
preethika.p329b8182020-04-22 12:25:39 +05301486
vijay.r35ef2f72019-04-30 17:55:49 +05301487 if item and topic != "pm_jobs":
tiernob24258a2018-10-04 18:39:49 +02001488 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +01001489
tiernob24258a2018-10-04 18:39:49 +02001490 if main_topic == "nsd":
1491 engine_topic = "nsds"
1492 elif main_topic == "vnfpkgm":
1493 engine_topic = "vnfds"
delacruzramo271d2002019-12-02 21:00:37 +01001494 if topic == "vnfpkg_op_occs":
1495 engine_topic = "vnfpkgops"
1496 if topic == "vnf_packages" and item == "action":
1497 engine_topic = "vnfpkgops"
tiernob24258a2018-10-04 18:39:49 +02001498 elif main_topic == "nslcm":
1499 engine_topic = "nsrs"
1500 if topic == "ns_lcm_op_occs":
1501 engine_topic = "nslcmops"
1502 if topic == "vnfrs" or topic == "vnf_instances":
1503 engine_topic = "vnfrs"
almagiae47b9132022-05-17 14:12:22 +02001504 elif main_topic == "vnflcm":
1505 if topic == "vnf_lcm_op_occs":
1506 engine_topic = "vnflcmops"
garciadeblas9750c5a2018-10-15 16:20:35 +02001507 elif main_topic == "nst":
1508 engine_topic = "nsts"
1509 elif main_topic == "nsilcm":
1510 engine_topic = "nsis"
1511 if topic == "nsi_lcm_op_occs":
delacruzramoc061f562019-04-05 11:00:02 +02001512 engine_topic = "nsilcmops"
tiernob24258a2018-10-04 18:39:49 +02001513 elif main_topic == "pdu":
1514 engine_topic = "pdus"
garciadeblas4568a372021-03-24 09:19:48 +01001515 if (
1516 engine_topic == "vims"
1517 ): # TODO this is for backward compatibility, it will be removed in the future
tiernob24258a2018-10-04 18:39:49 +02001518 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +01001519
preethika.p329b8182020-04-22 12:25:39 +05301520 if topic == "subscriptions":
1521 engine_topic = main_topic + "_" + topic
1522
tiernoc94c3df2018-02-09 15:38:54 +01001523 if method == "GET":
garciadeblas4568a372021-03-24 09:19:48 +01001524 if item in (
1525 "nsd_content",
1526 "package_content",
1527 "artifacts",
1528 "vnfd",
1529 "nsd",
1530 "nst",
1531 "nst_content",
1532 ):
garciadeblas9750c5a2018-10-15 16:20:35 +02001533 if item in ("vnfd", "nsd", "nst"):
tiernof27c79b2018-03-12 17:08:42 +01001534 path = "$DESCRIPTOR"
1535 elif args:
1536 path = args
tiernob24258a2018-10-04 18:39:49 +02001537 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +01001538 path = ()
1539 else:
1540 path = None
garciadeblas4568a372021-03-24 09:19:48 +01001541 file, _format = self.engine.get_file(
1542 engine_session,
1543 engine_topic,
1544 _id,
1545 path,
1546 cherrypy.request.headers.get("Accept"),
1547 )
tiernof27c79b2018-03-12 17:08:42 +01001548 outdata = file
1549 elif not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001550 outdata = self.engine.get_item_list(
1551 engine_session, engine_topic, kwargs, api_req=True
1552 )
tiernoc94c3df2018-02-09 15:38:54 +01001553 else:
vijay.r35ef2f72019-04-30 17:55:49 +05301554 if item == "reports":
1555 # TODO check that project_id (_id in this context) has permissions
1556 _id = args[0]
K Sai Kiran57589552021-01-27 21:38:34 +05301557 filter_q = None
1558 if "vcaStatusRefresh" in kwargs:
1559 filter_q = {"vcaStatusRefresh": kwargs["vcaStatusRefresh"]}
garciadeblasf2af4a12023-01-24 16:56:54 +01001560 outdata = self.engine.get_item(
1561 engine_session, engine_topic, _id, filter_q, True
1562 )
delacruzramo271d2002019-12-02 21:00:37 +01001563
tiernof27c79b2018-03-12 17:08:42 +01001564 elif method == "POST":
tiernobdebce92019-07-01 15:36:49 +00001565 cherrypy.response.status = HTTPStatus.CREATED.value
garciadeblas4568a372021-03-24 09:19:48 +01001566 if topic in (
1567 "ns_descriptors_content",
1568 "vnf_packages_content",
1569 "netslice_templates_content",
1570 ):
tiernof27c79b2018-03-12 17:08:42 +01001571 _id = cherrypy.request.headers.get("Transaction-Id")
1572 if not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001573 _id, _ = self.engine.new_item(
1574 rollback,
1575 engine_session,
1576 engine_topic,
1577 {},
1578 None,
1579 cherrypy.request.headers,
1580 )
1581 completed = self.engine.upload_content(
1582 engine_session,
1583 engine_topic,
1584 _id,
1585 indata,
1586 kwargs,
1587 cherrypy.request.headers,
1588 )
tiernof27c79b2018-03-12 17:08:42 +01001589 if completed:
tiernob24258a2018-10-04 18:39:49 +02001590 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001591 else:
1592 cherrypy.response.headers["Transaction-Id"] = _id
1593 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +02001594 elif topic == "ns_instances_content":
1595 # creates NSR
garciadeblas4568a372021-03-24 09:19:48 +01001596 _id, _ = self.engine.new_item(
1597 rollback, engine_session, engine_topic, indata, kwargs
1598 )
tiernob24258a2018-10-04 18:39:49 +02001599 # creates nslcmop
1600 indata["lcmOperationType"] = "instantiate"
1601 indata["nsInstanceId"] = _id
Adurtie1f864e2023-11-09 11:06:06 +00001602 nslcmop_id, nsName, _ = self.engine.new_item(
garciadeblas4568a372021-03-24 09:19:48 +01001603 rollback, engine_session, "nslcmops", indata, None
1604 )
tiernob24258a2018-10-04 18:39:49 +02001605 self._set_location_header(main_topic, version, topic, _id)
Adurtie1f864e2023-11-09 11:06:06 +00001606 outdata = {"id": _id, "nslcmop_id": nslcmop_id, "nsName": nsName}
tiernob24258a2018-10-04 18:39:49 +02001607 elif topic == "ns_instances" and item:
1608 indata["lcmOperationType"] = item
1609 indata["nsInstanceId"] = _id
Adurtie1f864e2023-11-09 11:06:06 +00001610 _id, nsName, _ = self.engine.new_item(
garciadeblas4568a372021-03-24 09:19:48 +01001611 rollback, engine_session, "nslcmops", indata, kwargs
1612 )
1613 self._set_location_header(
1614 main_topic, version, "ns_lcm_op_occs", _id
1615 )
Adurtie1f864e2023-11-09 11:06:06 +00001616 outdata = {"id": _id, "nsName": nsName}
tierno65acb4d2018-04-06 16:42:40 +02001617 cherrypy.response.status = HTTPStatus.ACCEPTED.value
garciadeblas9750c5a2018-10-15 16:20:35 +02001618 elif topic == "netslice_instances_content":
Felipe Vicens07f31722018-10-29 15:16:44 +01001619 # creates NetSlice_Instance_record (NSIR)
garciadeblas4568a372021-03-24 09:19:48 +01001620 _id, _ = self.engine.new_item(
1621 rollback, engine_session, engine_topic, indata, kwargs
1622 )
Felipe Vicens07f31722018-10-29 15:16:44 +01001623 self._set_location_header(main_topic, version, topic, _id)
garciadeblas9750c5a2018-10-15 16:20:35 +02001624 indata["lcmOperationType"] = "instantiate"
Felipe Vicens126af572019-06-05 19:13:04 +02001625 indata["netsliceInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001626 nsilcmop_id, _ = self.engine.new_item(
1627 rollback, engine_session, "nsilcmops", indata, kwargs
1628 )
kuuse078f55e2019-05-16 19:24:21 +02001629 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
garciadeblas9750c5a2018-10-15 16:20:35 +02001630 elif topic == "netslice_instances" and item:
1631 indata["lcmOperationType"] = item
Felipe Vicens126af572019-06-05 19:13:04 +02001632 indata["netsliceInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001633 _id, _ = self.engine.new_item(
1634 rollback, engine_session, "nsilcmops", indata, kwargs
1635 )
1636 self._set_location_header(
1637 main_topic, version, "nsi_lcm_op_occs", _id
1638 )
garciadeblas9750c5a2018-10-15 16:20:35 +02001639 outdata = {"id": _id}
1640 cherrypy.response.status = HTTPStatus.ACCEPTED.value
delacruzramo271d2002019-12-02 21:00:37 +01001641 elif topic == "vnf_packages" and item == "action":
1642 indata["lcmOperationType"] = item
1643 indata["vnfPkgId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001644 _id, _ = self.engine.new_item(
1645 rollback, engine_session, "vnfpkgops", indata, kwargs
1646 )
1647 self._set_location_header(
1648 main_topic, version, "vnfpkg_op_occs", _id
1649 )
delacruzramo271d2002019-12-02 21:00:37 +01001650 outdata = {"id": _id}
1651 cherrypy.response.status = HTTPStatus.ACCEPTED.value
preethika.p329b8182020-04-22 12:25:39 +05301652 elif topic == "subscriptions":
garciadeblas4568a372021-03-24 09:19:48 +01001653 _id, _ = self.engine.new_item(
1654 rollback, engine_session, engine_topic, indata, kwargs
1655 )
preethika.p329b8182020-04-22 12:25:39 +05301656 self._set_location_header(main_topic, version, topic, _id)
1657 link = {}
1658 link["self"] = cherrypy.response.headers["Location"]
garciadeblas4568a372021-03-24 09:19:48 +01001659 outdata = {
1660 "id": _id,
1661 "filter": indata["filter"],
1662 "callbackUri": indata["CallbackUri"],
1663 "_links": link,
1664 }
preethika.p329b8182020-04-22 12:25:39 +05301665 cherrypy.response.status = HTTPStatus.CREATED.value
almagiae47b9132022-05-17 14:12:22 +02001666 elif topic == "vnf_instances" and item:
1667 indata["lcmOperationType"] = item
1668 indata["vnfInstanceId"] = _id
Adurtie1f864e2023-11-09 11:06:06 +00001669 _id, nsName, _ = self.engine.new_item(
garciadeblasf2af4a12023-01-24 16:56:54 +01001670 rollback, engine_session, "vnflcmops", indata, kwargs
1671 )
1672 self._set_location_header(
1673 main_topic, version, "vnf_lcm_op_occs", _id
1674 )
Adurtie1f864e2023-11-09 11:06:06 +00001675 outdata = {"id": _id, "nsName": nsName}
almagiae47b9132022-05-17 14:12:22 +02001676 cherrypy.response.status = HTTPStatus.ACCEPTED.value
Gabriel Cuba84a60df2023-10-30 14:01:54 -05001677 elif topic == "ns_lcm_op_occs" and item == "cancel":
1678 indata["nsLcmOpOccId"] = _id
1679 self.engine.cancel_item(
1680 rollback, engine_session, "nslcmops", indata, None
1681 )
1682 self._set_location_header(main_topic, version, topic, _id)
1683 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +01001684 else:
garciadeblas4568a372021-03-24 09:19:48 +01001685 _id, op_id = self.engine.new_item(
1686 rollback,
1687 engine_session,
1688 engine_topic,
1689 indata,
1690 kwargs,
1691 cherrypy.request.headers,
1692 )
tiernob24258a2018-10-04 18:39:49 +02001693 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001694 outdata = {"id": _id}
tiernobdebce92019-07-01 15:36:49 +00001695 if op_id:
1696 outdata["op_id"] = op_id
1697 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernob24258a2018-10-04 18:39:49 +02001698 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tierno09c073e2018-04-26 13:36:48 +02001699
tiernoc94c3df2018-02-09 15:38:54 +01001700 elif method == "DELETE":
1701 if not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001702 outdata = self.engine.del_item_list(
1703 engine_session, engine_topic, kwargs
1704 )
tierno09c073e2018-04-26 13:36:48 +02001705 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +01001706 else: # len(args) > 1
tierno22577432020-04-08 15:16:57 +00001707 # for NS NSI generate an operation
1708 op_id = None
tierno701018c2019-06-25 11:13:14 +00001709 if topic == "ns_instances_content" and not engine_session["force"]:
tiernob24258a2018-10-04 18:39:49 +02001710 nslcmop_desc = {
1711 "lcmOperationType": "terminate",
1712 "nsInstanceId": _id,
garciadeblas4568a372021-03-24 09:19:48 +01001713 "autoremove": True,
tiernob24258a2018-10-04 18:39:49 +02001714 }
Adurtie1f864e2023-11-09 11:06:06 +00001715 op_id, nsName, _ = self.engine.new_item(
garciadeblas4568a372021-03-24 09:19:48 +01001716 rollback, engine_session, "nslcmops", nslcmop_desc, kwargs
1717 )
tierno22577432020-04-08 15:16:57 +00001718 if op_id:
Adurtie1f864e2023-11-09 11:06:06 +00001719 outdata = {"_id": op_id, "nsName": nsName}
garciadeblas4568a372021-03-24 09:19:48 +01001720 elif (
1721 topic == "netslice_instances_content"
1722 and not engine_session["force"]
1723 ):
garciadeblas9750c5a2018-10-15 16:20:35 +02001724 nsilcmop_desc = {
1725 "lcmOperationType": "terminate",
Felipe Vicens126af572019-06-05 19:13:04 +02001726 "netsliceInstanceId": _id,
garciadeblas4568a372021-03-24 09:19:48 +01001727 "autoremove": True,
garciadeblas9750c5a2018-10-15 16:20:35 +02001728 }
garciadeblas4568a372021-03-24 09:19:48 +01001729 op_id, _ = self.engine.new_item(
1730 rollback, engine_session, "nsilcmops", nsilcmop_desc, None
1731 )
tierno22577432020-04-08 15:16:57 +00001732 if op_id:
1733 outdata = {"_id": op_id}
1734 # if there is not any deletion in process, delete
1735 if not op_id:
1736 op_id = self.engine.del_item(engine_session, engine_topic, _id)
1737 if op_id:
1738 outdata = {"op_id": op_id}
garciadeblas4568a372021-03-24 09:19:48 +01001739 cherrypy.response.status = (
1740 HTTPStatus.ACCEPTED.value
1741 if op_id
1742 else HTTPStatus.NO_CONTENT.value
1743 )
tierno09c073e2018-04-26 13:36:48 +02001744
tierno7ae10112018-05-18 14:36:02 +02001745 elif method in ("PUT", "PATCH"):
tiernobdebce92019-07-01 15:36:49 +00001746 op_id = None
tierno701018c2019-06-25 11:13:14 +00001747 if not indata and not kwargs and not engine_session.get("set_project"):
garciadeblas4568a372021-03-24 09:19:48 +01001748 raise NbiException(
1749 "Nothing to update. Provide payload and/or query string",
1750 HTTPStatus.BAD_REQUEST,
1751 )
1752 if (
1753 item in ("nsd_content", "package_content", "nst_content")
1754 and method == "PUT"
1755 ):
1756 completed = self.engine.upload_content(
1757 engine_session,
1758 engine_topic,
1759 _id,
1760 indata,
1761 kwargs,
1762 cherrypy.request.headers,
1763 )
tiernof27c79b2018-03-12 17:08:42 +01001764 if not completed:
1765 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +01001766 else:
garciadeblas4568a372021-03-24 09:19:48 +01001767 op_id = self.engine.edit_item(
1768 engine_session, engine_topic, _id, indata, kwargs
1769 )
tiernobdebce92019-07-01 15:36:49 +00001770
1771 if op_id:
1772 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1773 outdata = {"op_id": op_id}
1774 else:
1775 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
1776 outdata = None
tiernoc94c3df2018-02-09 15:38:54 +01001777 else:
garciadeblas4568a372021-03-24 09:19:48 +01001778 raise NbiException(
1779 "Method {} not allowed".format(method),
1780 HTTPStatus.METHOD_NOT_ALLOWED,
1781 )
tiernoa6bb45d2019-06-14 09:45:39 +00001782
1783 # if Role information changes, it is needed to reload the information of roles
1784 if topic == "roles" and method != "GET":
1785 self.authenticator.load_operation_to_allowed_roles()
delacruzramoad682a52019-12-10 16:26:34 +01001786
garciadeblas4568a372021-03-24 09:19:48 +01001787 if (
1788 topic == "projects"
1789 and method == "DELETE"
1790 or topic in ["users", "roles"]
1791 and method in ["PUT", "PATCH", "DELETE"]
1792 ):
delacruzramoad682a52019-12-10 16:26:34 +01001793 self.authenticator.remove_token_from_cache()
1794
Adurtie1f864e2023-11-09 11:06:06 +00001795 cef_event(
1796 cef_logger,
1797 {
1798 "name": "User Operation",
1799 "sourceUserName": token_info.get("username"),
1800 },
1801 )
1802 if topic == "ns_instances_content" and url_id:
1803 nsName = (
1804 outdata.get("name") if method == "GET" else outdata.get("nsName")
1805 )
elumalai7802ff82023-04-24 20:38:32 +05301806 cef_event(
1807 cef_logger,
1808 {
Adurtie1f864e2023-11-09 11:06:06 +00001809 "message": "{} {}, nsName={}, nsdId={}, Project={} Outcome=Success".format(
1810 log_mapping[method],
1811 topic,
1812 nsName,
1813 outdata.get("id"),
1814 token_info.get("project_name"),
1815 ),
1816 },
1817 )
1818 cherrypy.log("{}".format(cef_logger))
1819 elif topic == "ns_instances_content" and method == "POST":
1820 cef_event(
1821 cef_logger,
1822 {
1823 "message": "{} {}, nsName={}, nsdId={}, Project={} Outcome=Success".format(
1824 log_mapping[method],
1825 topic,
1826 outdata.get("nsName"),
1827 outdata.get("id"),
1828 token_info.get("project_name"),
1829 ),
1830 },
1831 )
1832 cherrypy.log("{}".format(cef_logger))
1833 elif topic in ("ns_instances", "vnf_instances") and item:
1834 cef_event(
1835 cef_logger,
1836 {
1837 "message": "{} {}, nsName={}, nsdId={}, Project={} Outcome=Success".format(
1838 log_mapping[method],
1839 topic,
1840 outdata.get("nsName"),
1841 url_id,
1842 token_info.get("project_name"),
1843 ),
1844 },
1845 )
1846 cherrypy.log("{}".format(cef_logger))
1847 elif item is not None:
1848 cef_event(
1849 cef_logger,
1850 {
elumalai7802ff82023-04-24 20:38:32 +05301851 "message": "Performing {} operation on {} {}, Project={} Outcome=Success".format(
1852 item,
1853 topic,
1854 url_id,
1855 token_info.get("project_name"),
1856 ),
1857 },
1858 )
1859 cherrypy.log("{}".format(cef_logger))
1860 else:
1861 cef_event(
1862 cef_logger,
1863 {
elumalai7802ff82023-04-24 20:38:32 +05301864 "message": "{} {} {}, Project={} Outcome=Success".format(
1865 log_mapping[method],
1866 topic,
1867 url_id,
1868 token_info.get("project_name"),
1869 ),
1870 },
1871 )
1872 cherrypy.log("{}".format(cef_logger))
tierno701018c2019-06-25 11:13:14 +00001873 return self._format_out(outdata, token_info, _format)
tiernob24258a2018-10-04 18:39:49 +02001874 except Exception as e:
garciadeblas4568a372021-03-24 09:19:48 +01001875 if isinstance(
1876 e,
1877 (
1878 NbiException,
1879 EngineException,
1880 DbException,
1881 FsException,
1882 MsgException,
1883 AuthException,
1884 ValidationError,
1885 AuthconnException,
1886 ),
1887 ):
tiernob24258a2018-10-04 18:39:49 +02001888 http_code_value = cherrypy.response.status = e.http_code.value
1889 http_code_name = e.http_code.name
1890 cherrypy.log("Exception {}".format(e))
1891 else:
garciadeblas4568a372021-03-24 09:19:48 +01001892 http_code_value = (
1893 cherrypy.response.status
1894 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Felipe Vicense36ab852018-11-23 14:12:09 +01001895 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
tiernob24258a2018-10-04 18:39:49 +02001896 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +02001897 if hasattr(outdata, "close"): # is an open file
1898 outdata.close()
tiernob24258a2018-10-04 18:39:49 +02001899 error_text = str(e)
1900 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +02001901 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +02001902 try:
tiernocc103432018-10-19 14:10:35 +02001903 if rollback_item.get("operation") == "set":
garciadeblas4568a372021-03-24 09:19:48 +01001904 self.engine.db.set_one(
1905 rollback_item["topic"],
1906 {"_id": rollback_item["_id"]},
1907 rollback_item["content"],
1908 fail_on_empty=False,
1909 )
preethika.p329b8182020-04-22 12:25:39 +05301910 elif rollback_item.get("operation") == "del_list":
garciadeblas4568a372021-03-24 09:19:48 +01001911 self.engine.db.del_list(
1912 rollback_item["topic"],
1913 rollback_item["filter"],
garciadeblas4568a372021-03-24 09:19:48 +01001914 )
tiernocc103432018-10-19 14:10:35 +02001915 else:
garciadeblas4568a372021-03-24 09:19:48 +01001916 self.engine.db.del_one(
1917 rollback_item["topic"],
1918 {"_id": rollback_item["_id"]},
1919 fail_on_empty=False,
1920 )
tierno3ace63c2018-05-03 17:51:43 +02001921 except Exception as e2:
garciadeblas4568a372021-03-24 09:19:48 +01001922 rollback_error_text = "Rollback Exception {}: {}".format(
1923 rollback_item, e2
1924 )
tiernob24258a2018-10-04 18:39:49 +02001925 cherrypy.log(rollback_error_text)
1926 error_text += ". " + rollback_error_text
1927 # if isinstance(e, MsgException):
1928 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1929 # engine_topic[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +01001930 problem_details = {
tiernob24258a2018-10-04 18:39:49 +02001931 "code": http_code_name,
1932 "status": http_code_value,
1933 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +01001934 }
elumalai7802ff82023-04-24 20:38:32 +05301935 if item is not None and token_info is not None:
1936 cef_event(
1937 cef_logger,
1938 {
1939 "name": "User Operation",
1940 "sourceUserName": token_info.get("username", None),
1941 "message": "Performing {} operation on {} {}, Project={} Outcome=Failure".format(
1942 item,
1943 topic,
1944 url_id,
1945 token_info.get("project_name", None),
1946 ),
1947 "severity": "2",
1948 },
1949 )
1950 cherrypy.log("{}".format(cef_logger))
1951 elif token_info is not None:
1952 cef_event(
1953 cef_logger,
1954 {
1955 "name": "User Operation",
1956 "sourceUserName": token_info.get("username", None),
1957 "message": "{} {} {}, Project={} Outcome=Failure".format(
1958 item,
1959 topic,
1960 url_id,
1961 token_info.get("project_name", None),
1962 ),
1963 "severity": "2",
1964 },
1965 )
1966 cherrypy.log("{}".format(cef_logger))
tierno701018c2019-06-25 11:13:14 +00001967 return self._format_out(problem_details, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001968 # raise cherrypy.HTTPError(e.http_code.value, str(e))
tiernoa5035702019-07-29 08:54:42 +00001969 finally:
1970 if token_info:
1971 self._format_login(token_info)
1972 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
1973 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1974 if outdata.get(logging_id):
garciadeblas4568a372021-03-24 09:19:48 +01001975 cherrypy.request.login += ";{}={}".format(
1976 logging_id, outdata[logging_id][:36]
1977 )
tiernoc94c3df2018-02-09 15:38:54 +01001978
1979
tiernoc94c3df2018-02-09 15:38:54 +01001980def _start_service():
1981 """
1982 Callback function called when cherrypy.engine starts
1983 Override configuration with env variables
1984 Set database, storage, message configuration
1985 Init database with admin/admin user password
1986 """
tierno932499c2019-01-28 17:28:10 +00001987 global nbi_server
1988 global subscription_thread
elumalai7802ff82023-04-24 20:38:32 +05301989 global cef_logger
Rahulc72bc8e2023-12-05 11:54:38 +00001990 global current_backend
tiernoc94c3df2018-02-09 15:38:54 +01001991 cherrypy.log.error("Starting osm_nbi")
1992 # update general cherrypy configuration
1993 update_dict = {}
1994
garciadeblas4568a372021-03-24 09:19:48 +01001995 engine_config = cherrypy.tree.apps["/osm"].config
tiernoc94c3df2018-02-09 15:38:54 +01001996 for k, v in environ.items():
garciadeblas6d83f8f2023-06-19 22:34:49 +02001997 if k == "OSMNBI_USER_MANAGEMENT":
1998 feature_state = eval(v.title())
1999 engine_config["authentication"]["user_management"] = feature_state
Adurtid68526d2023-11-14 11:14:53 +00002000 elif k == "OSMNBI_PWD_EXPIRE_DAYS":
2001 pwd_expire_days = int(v)
2002 engine_config["authentication"]["pwd_expire_days"] = pwd_expire_days
2003 elif k == "OSMNBI_MAX_PWD_ATTEMPT":
2004 max_pwd_attempt = int(v)
2005 engine_config["authentication"]["max_pwd_attempt"] = max_pwd_attempt
2006 elif k == "OSMNBI_ACCOUNT_EXPIRE_DAYS":
2007 account_expire_days = int(v)
2008 engine_config["authentication"]["account_expire_days"] = account_expire_days
tiernoc94c3df2018-02-09 15:38:54 +01002009 if not k.startswith("OSMNBI_"):
2010 continue
tiernoe1281182018-05-22 12:24:36 +02002011 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +01002012 if not k2:
2013 continue
2014 try:
2015 # update static configuration
garciadeblas4568a372021-03-24 09:19:48 +01002016 if k == "OSMNBI_STATIC_DIR":
2017 engine_config["/static"]["tools.staticdir.dir"] = v
2018 engine_config["/static"]["tools.staticdir.on"] = True
2019 elif k == "OSMNBI_SOCKET_PORT" or k == "OSMNBI_SERVER_PORT":
2020 update_dict["server.socket_port"] = int(v)
2021 elif k == "OSMNBI_SOCKET_HOST" or k == "OSMNBI_SERVER_HOST":
2022 update_dict["server.socket_host"] = v
tiernof5298be2018-05-16 14:43:57 +02002023 elif k1 in ("server", "test", "auth", "log"):
garciadeblas4568a372021-03-24 09:19:48 +01002024 update_dict[k1 + "." + k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01002025 elif k1 in ("message", "database", "storage", "authentication"):
tiernof5298be2018-05-16 14:43:57 +02002026 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +01002027 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +01002028 engine_config[k1][k2] = int(v)
2029 else:
2030 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01002031
tiernoc94c3df2018-02-09 15:38:54 +01002032 except ValueError as e:
2033 cherrypy.log.error("Ignoring environ '{}': " + str(e))
2034 except Exception as e:
garciadeblasf2af4a12023-01-24 16:56:54 +01002035 cherrypy.log(
2036 "WARNING: skipping environ '{}' on exception '{}'".format(k, e)
2037 )
tiernoc94c3df2018-02-09 15:38:54 +01002038
2039 if update_dict:
2040 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +02002041 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +01002042
2043 # logging cherrypy
garciadeblas4568a372021-03-24 09:19:48 +01002044 log_format_simple = (
2045 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
2046 )
2047 log_formatter_simple = logging.Formatter(
2048 log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S"
2049 )
tiernoc94c3df2018-02-09 15:38:54 +01002050 logger_server = logging.getLogger("cherrypy.error")
2051 logger_access = logging.getLogger("cherrypy.access")
2052 logger_cherry = logging.getLogger("cherrypy")
2053 logger_nbi = logging.getLogger("nbi")
2054
tiernof5298be2018-05-16 14:43:57 +02002055 if "log.file" in engine_config["global"]:
garciadeblas4568a372021-03-24 09:19:48 +01002056 file_handler = logging.handlers.RotatingFileHandler(
2057 engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0
2058 )
tiernoc94c3df2018-02-09 15:38:54 +01002059 file_handler.setFormatter(log_formatter_simple)
2060 logger_cherry.addHandler(file_handler)
2061 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +02002062 # log always to standard output
garciadeblas4568a372021-03-24 09:19:48 +01002063 for format_, logger in {
2064 "nbi.server %(filename)s:%(lineno)s": logger_server,
2065 "nbi.access %(filename)s:%(lineno)s": logger_access,
2066 "%(name)s %(filename)s:%(lineno)s": logger_nbi,
2067 }.items():
tiernob24258a2018-10-04 18:39:49 +02002068 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
garciadeblas4568a372021-03-24 09:19:48 +01002069 log_formatter_cherry = logging.Formatter(
2070 log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S"
2071 )
tiernob24258a2018-10-04 18:39:49 +02002072 str_handler = logging.StreamHandler()
2073 str_handler.setFormatter(log_formatter_cherry)
2074 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +01002075
tiernof5298be2018-05-16 14:43:57 +02002076 if engine_config["global"].get("log.level"):
2077 logger_cherry.setLevel(engine_config["global"]["log.level"])
2078 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +01002079
2080 # logging other modules
garciadeblas4568a372021-03-24 09:19:48 +01002081 for k1, logname in {
2082 "message": "nbi.msg",
2083 "database": "nbi.db",
2084 "storage": "nbi.fs",
2085 }.items():
tiernoc94c3df2018-02-09 15:38:54 +01002086 engine_config[k1]["logger_name"] = logname
2087 logger_module = logging.getLogger(logname)
2088 if "logfile" in engine_config[k1]:
garciadeblas4568a372021-03-24 09:19:48 +01002089 file_handler = logging.handlers.RotatingFileHandler(
2090 engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0
2091 )
tiernoc94c3df2018-02-09 15:38:54 +01002092 file_handler.setFormatter(log_formatter_simple)
2093 logger_module.addHandler(file_handler)
2094 if "loglevel" in engine_config[k1]:
2095 logger_module.setLevel(engine_config[k1]["loglevel"])
2096 # TODO add more entries, e.g.: storage
garciadeblas4568a372021-03-24 09:19:48 +01002097 cherrypy.tree.apps["/osm"].root.engine.start(engine_config)
2098 cherrypy.tree.apps["/osm"].root.authenticator.start(engine_config)
2099 cherrypy.tree.apps["/osm"].root.engine.init_db(target_version=database_version)
2100 cherrypy.tree.apps["/osm"].root.authenticator.init_db(
2101 target_version=auth_database_version
2102 )
tiernobee508e2019-01-21 11:21:49 +00002103
elumalai7802ff82023-04-24 20:38:32 +05302104 cef_logger = cef_event_builder(engine_config["authentication"])
2105
tierno932499c2019-01-28 17:28:10 +00002106 # start subscriptions thread:
garciadeblas4568a372021-03-24 09:19:48 +01002107 subscription_thread = SubscriptionThread(
2108 config=engine_config, engine=nbi_server.engine
2109 )
tierno932499c2019-01-28 17:28:10 +00002110 subscription_thread.start()
2111 # Do not capture except SubscriptionException
2112
tiernob2e48bd2020-02-04 15:47:18 +00002113 backend = engine_config["authentication"]["backend"]
Rahulc72bc8e2023-12-05 11:54:38 +00002114 current_backend = backend
garciadeblas4568a372021-03-24 09:19:48 +01002115 cherrypy.log.error(
2116 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
2117 nbi_version, nbi_version_date, backend
2118 )
2119 )
tiernoc94c3df2018-02-09 15:38:54 +01002120
2121
2122def _stop_service():
2123 """
2124 Callback function called when cherrypy.engine stops
2125 TODO: Ending database connections.
2126 """
tierno932499c2019-01-28 17:28:10 +00002127 global subscription_thread
tierno65ca36d2019-02-12 19:27:52 +01002128 if subscription_thread:
2129 subscription_thread.terminate()
tierno932499c2019-01-28 17:28:10 +00002130 subscription_thread = None
garciadeblas4568a372021-03-24 09:19:48 +01002131 cherrypy.tree.apps["/osm"].root.engine.stop()
tiernoc94c3df2018-02-09 15:38:54 +01002132 cherrypy.log.error("Stopping osm_nbi")
2133
tierno2236d202018-05-16 19:05:16 +02002134
tiernof5298be2018-05-16 14:43:57 +02002135def nbi(config_file):
tierno932499c2019-01-28 17:28:10 +00002136 global nbi_server
tiernoc94c3df2018-02-09 15:38:54 +01002137 # conf = {
2138 # '/': {
2139 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
2140 # 'tools.sessions.on': True,
2141 # 'tools.response_headers.on': True,
2142 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
2143 # }
2144 # }
2145 # cherrypy.Server.ssl_module = 'builtin'
2146 # cherrypy.Server.ssl_certificate = "http/cert.pem"
2147 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
2148 # cherrypy.Server.thread_pool = 10
2149 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
2150
2151 # cherrypy.config.update({'tools.auth_basic.on': True,
2152 # 'tools.auth_basic.realm': 'localhost',
2153 # 'tools.auth_basic.checkpassword': validate_password})
tierno932499c2019-01-28 17:28:10 +00002154 nbi_server = Server()
garciadeblas4568a372021-03-24 09:19:48 +01002155 cherrypy.engine.subscribe("start", _start_service)
2156 cherrypy.engine.subscribe("stop", _stop_service)
2157 cherrypy.quickstart(nbi_server, "/osm", config_file)
tiernof5298be2018-05-16 14:43:57 +02002158
2159
2160def usage():
garciadeblas4568a372021-03-24 09:19:48 +01002161 print(
2162 """Usage: {} [options]
tiernof5298be2018-05-16 14:43:57 +02002163 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
2164 -h|--help: shows this help
garciadeblas4568a372021-03-24 09:19:48 +01002165 """.format(
2166 sys.argv[0]
2167 )
2168 )
tierno2236d202018-05-16 19:05:16 +02002169 # --log-socket-host HOST: send logs to this host")
2170 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +01002171
2172
garciadeblas4568a372021-03-24 09:19:48 +01002173if __name__ == "__main__":
tiernof5298be2018-05-16 14:43:57 +02002174 try:
2175 # load parameters and configuration
2176 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
2177 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
2178 config_file = None
2179 for o, a in opts:
2180 if o in ("-h", "--help"):
2181 usage()
2182 sys.exit()
2183 elif o in ("-c", "--config"):
2184 config_file = a
2185 # elif o == "--log-socket-port":
2186 # log_socket_port = a
2187 # elif o == "--log-socket-host":
2188 # log_socket_host = a
2189 # elif o == "--log-file":
2190 # log_file = a
2191 else:
2192 assert False, "Unhandled option"
2193 if config_file:
2194 if not path.isfile(config_file):
garciadeblas4568a372021-03-24 09:19:48 +01002195 print(
2196 "configuration file '{}' that not exist".format(config_file),
2197 file=sys.stderr,
2198 )
tiernof5298be2018-05-16 14:43:57 +02002199 exit(1)
2200 else:
garciadeblas4568a372021-03-24 09:19:48 +01002201 for config_file in (
2202 __file__[: __file__.rfind(".")] + ".cfg",
2203 "./nbi.cfg",
2204 "/etc/osm/nbi.cfg",
2205 ):
tiernof5298be2018-05-16 14:43:57 +02002206 if path.isfile(config_file):
2207 break
2208 else:
garciadeblas4568a372021-03-24 09:19:48 +01002209 print(
2210 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
2211 file=sys.stderr,
2212 )
tiernof5298be2018-05-16 14:43:57 +02002213 exit(1)
2214 nbi(config_file)
2215 except getopt.GetoptError as e:
2216 print(str(e), file=sys.stderr)
2217 # usage()
2218 exit(1)