blob: fed3205137440e70dc08733bda0c3f4104e8d756 [file] [log] [blame]
tiernoc94c3df2018-02-09 15:38:54 +01001#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3
tiernod125caf2018-11-22 16:05:54 +00004# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
tiernoc94c3df2018-02-09 15:38:54 +010017import cherrypy
18import time
19import json
20import yaml
tierno9c630112019-08-29 14:21:41 +000021import osm_nbi.html_out as html
tiernoc94c3df2018-02-09 15:38:54 +010022import logging
tiernof5298be2018-05-16 14:43:57 +020023import logging.handlers
24import getopt
25import sys
Eduardo Sousa2f988212018-07-26 01:04:11 +010026
tierno9c630112019-08-29 14:21:41 +000027from osm_nbi.authconn import AuthException, AuthconnException
28from osm_nbi.auth import Authenticator
29from osm_nbi.engine import Engine, EngineException
30from osm_nbi.subscriptions import SubscriptionThread
31from osm_nbi.validation import ValidationError
tiernoa8d63632018-05-10 13:12:32 +020032from osm_common.dbbase import DbException
33from osm_common.fsbase import FsException
34from osm_common.msgbase import MsgException
Patricia Reinoso62fa6732023-02-22 17:57:53 +000035from osm_common.wftemporal import WFTemporal
tiernoc94c3df2018-02-09 15:38:54 +010036from http import HTTPStatus
tiernoc94c3df2018-02-09 15:38:54 +010037from codecs import getreader
tiernof5298be2018-05-16 14:43:57 +020038from os import environ, path
tiernob2e48bd2020-02-04 15:47:18 +000039from osm_nbi import version as nbi_version, version_date as nbi_version_date
tiernoc94c3df2018-02-09 15:38:54 +010040
41__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
tiernodfe09572018-04-24 10:41:10 +020042
garciadeblas4568a372021-03-24 09:19:48 +010043__version__ = "0.1.3" # file version, not NBI version
tierno9c630112019-08-29 14:21:41 +000044version_date = "Aug 2019"
45
garciadeblas4568a372021-03-24 09:19:48 +010046database_version = "1.2"
47auth_database_version = "1.0"
48nbi_server = None # instance of Server class
tierno932499c2019-01-28 17:28:10 +000049subscription_thread = None # instance of SubscriptionThread class
tiernoc94c3df2018-02-09 15:38:54 +010050
51"""
tiernof27c79b2018-03-12 17:08:42 +010052North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
tiernoc94c3df2018-02-09 15:38:54 +010053URL: /osm GET POST PUT DELETE PATCH
garciadeblas9750c5a2018-10-15 16:20:35 +020054 /nsd/v1
tierno2236d202018-05-16 19:05:16 +020055 /ns_descriptors_content O O
56 /<nsdInfoId> O O O O
tiernoc94c3df2018-02-09 15:38:54 +010057 /ns_descriptors O5 O5
58 /<nsdInfoId> O5 O5 5
59 /nsd_content O5 O5
tiernof27c79b2018-03-12 17:08:42 +010060 /nsd O
61 /artifacts[/<artifactPath>] O
tiernoc94c3df2018-02-09 15:38:54 +010062 /pnf_descriptors 5 5
63 /<pnfdInfoId> 5 5 5
64 /pnfd_content 5 5
tiernof27c79b2018-03-12 17:08:42 +010065 /subscriptions 5 5
66 /<subscriptionId> 5 X
tiernoc94c3df2018-02-09 15:38:54 +010067
68 /vnfpkgm/v1
tierno55945e72018-04-06 16:40:27 +020069 /vnf_packages_content O O
tierno2236d202018-05-16 19:05:16 +020070 /<vnfPkgId> O O
tiernoc94c3df2018-02-09 15:38:54 +010071 /vnf_packages O5 O5
72 /<vnfPkgId> O5 O5 5
tiernoc94c3df2018-02-09 15:38:54 +010073 /package_content O5 O5
74 /upload_from_uri X
tiernof27c79b2018-03-12 17:08:42 +010075 /vnfd O5
76 /artifacts[/<artifactPath>] O5
77 /subscriptions X X
78 /<subscriptionId> X X
tiernoc94c3df2018-02-09 15:38:54 +010079
80 /nslcm/v1
tiernof27c79b2018-03-12 17:08:42 +010081 /ns_instances_content O O
tierno2236d202018-05-16 19:05:16 +020082 /<nsInstanceId> O O
tiernof27c79b2018-03-12 17:08:42 +010083 /ns_instances 5 5
tierno95692442018-05-24 18:05:28 +020084 /<nsInstanceId> O5 O5
tierno65acb4d2018-04-06 16:42:40 +020085 instantiate O5
86 terminate O5
87 action O
88 scale O5
elumalai8e3806c2022-04-28 17:26:24 +053089 migrate O
aticig544a2ae2022-04-05 09:00:17 +030090 update 05
garciadeblas0964edf2022-02-11 00:43:44 +010091 heal O5
tiernoc94c3df2018-02-09 15:38:54 +010092 /ns_lcm_op_occs 5 5
93 /<nsLcmOpOccId> 5 5 5
94 TO BE COMPLETED 5 5
tiernof759d822018-06-11 18:54:54 +020095 /vnf_instances (also vnfrs for compatibility) O
96 /<vnfInstanceId> O
tiernof27c79b2018-03-12 17:08:42 +010097 /subscriptions 5 5
98 /<subscriptionId> 5 X
garciadeblas9750c5a2018-10-15 16:20:35 +020099
tiernocb83c942018-09-24 17:28:13 +0200100 /pdu/v1
tierno032916c2019-03-22 13:27:12 +0000101 /pdu_descriptors O O
tiernocb83c942018-09-24 17:28:13 +0200102 /<id> O O O O
garciadeblas9750c5a2018-10-15 16:20:35 +0200103
tiernof27c79b2018-03-12 17:08:42 +0100104 /admin/v1
105 /tokens O O
tierno2236d202018-05-16 19:05:16 +0200106 /<id> O O
tiernof27c79b2018-03-12 17:08:42 +0100107 /users O O
tiernocd54a4a2018-09-12 16:40:35 +0200108 /<id> O O O O
tiernof27c79b2018-03-12 17:08:42 +0100109 /projects O O
tierno2236d202018-05-16 19:05:16 +0200110 /<id> O O
tierno55ba2e62018-12-11 17:22:22 +0000111 /vim_accounts (also vims for compatibility) O O
112 /<id> O O O
113 /wim_accounts O O
tierno2236d202018-05-16 19:05:16 +0200114 /<id> O O O
tierno0f98af52018-03-19 10:28:22 +0100115 /sdns O O
tierno2236d202018-05-16 19:05:16 +0200116 /<id> O O O
delacruzramofe598fe2019-10-23 18:25:11 +0200117 /k8sclusters O O
118 /<id> O O O
119 /k8srepos O O
120 /<id> O O
Felipe Vicensb66b0412020-05-06 10:11:00 +0200121 /osmrepos O O
122 /<id> O O
tiernoc94c3df2018-02-09 15:38:54 +0100123
garciadeblas9750c5a2018-10-15 16:20:35 +0200124 /nst/v1 O O
125 /netslice_templates_content O O
126 /<nstInfoId> O O O O
127 /netslice_templates O O
128 /<nstInfoId> O O O
129 /nst_content O O
130 /nst O
131 /artifacts[/<artifactPath>] O
132 /subscriptions X X
133 /<subscriptionId> X X
134
135 /nsilcm/v1
136 /netslice_instances_content O O
137 /<SliceInstanceId> O O
138 /netslice_instances O O
139 /<SliceInstanceId> O O
140 instantiate O
141 terminate O
142 action O
143 /nsi_lcm_op_occs O O
144 /<nsiLcmOpOccId> O O O
145 /subscriptions X X
146 /<subscriptionId> X X
147
tierno2236d202018-05-16 19:05:16 +0200148query string:
149 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
tierno36ec8602018-11-02 17:27:11 +0100150 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
151 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
152 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
153 attrName := string
tierno2236d202018-05-16 19:05:16 +0200154 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
155 item of the array, that is, pass if any item of the array pass the filter.
156 It allows both ne and neq for not equal
157 TODO: 4.3.3 Attribute selectors
158 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
tiernoc94c3df2018-02-09 15:38:54 +0100159 (none) … same as “exclude_default”
160 all_fields … all attributes.
tierno2236d202018-05-16 19:05:16 +0200161 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
162 conditionally mandatory, and that are not provided in <list>.
163 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
164 are not conditionally mandatory, and that are provided in <list>.
165 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
166 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
167 the particular resource
168 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
169 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
170 present specification for the particular resource, but that are not part of <list>
tierno65ca36d2019-02-12 19:27:52 +0100171 Additionally it admits some administrator values:
172 FORCE: To force operations skipping dependency checkings
173 ADMIN: To act as an administrator or a different project
174 PUBLIC: To get public descriptors or set a descriptor as public
175 SET_PROJECT: To make a descriptor available for other project
beierlmbc5a5242022-05-17 21:25:29 -0400176
tiernoc94c3df2018-02-09 15:38:54 +0100177Header field name Reference Example Descriptions
178 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
179 This header field shall be present if the response is expected to have a non-empty message body.
180 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
181 This header field shall be present if the request has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200182 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
183 Details are specified in clause 4.5.3.
tiernoc94c3df2018-02-09 15:38:54 +0100184 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
185Header field name Reference Example Descriptions
186 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
187 This header field shall be present if the response has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200188 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
189 new resource has been created.
tiernoc94c3df2018-02-09 15:38:54 +0100190 This header field shall be present if the response status code is 201 or 3xx.
tierno2236d202018-05-16 19:05:16 +0200191 In the present document this header field is also used if the response status code is 202 and a new resource was
192 created.
193 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
194 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
195 token.
196 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
197 certain resources.
198 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
199 response, and the total length of the file.
tiernoc94c3df2018-02-09 15:38:54 +0100200 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
tiernoc94c3df2018-02-09 15:38:54 +0100201"""
202
tierno701018c2019-06-25 11:13:14 +0000203valid_query_string = ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
204# ^ Contains possible administrative query string words:
205# ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
206# (not owned by my session project).
207# PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
208# FORCE=True(by default)|False: Force edition/deletion operations
209# SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
210
211valid_url_methods = {
212 # contains allowed URL and methods, and the role_permission name
213 "admin": {
214 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100215 "tokens": {
216 "METHODS": ("GET", "POST", "DELETE"),
217 "ROLE_PERMISSION": "tokens:",
218 "<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"},
219 },
220 "users": {
221 "METHODS": ("GET", "POST"),
222 "ROLE_PERMISSION": "users:",
223 "<ID>": {
224 "METHODS": ("GET", "DELETE", "PATCH"),
225 "ROLE_PERMISSION": "users:id:",
226 },
227 },
228 "projects": {
229 "METHODS": ("GET", "POST"),
230 "ROLE_PERMISSION": "projects:",
231 "<ID>": {
232 "METHODS": ("GET", "DELETE", "PATCH"),
233 "ROLE_PERMISSION": "projects:id:",
234 },
235 },
236 "roles": {
237 "METHODS": ("GET", "POST"),
238 "ROLE_PERMISSION": "roles:",
239 "<ID>": {
240 "METHODS": ("GET", "DELETE", "PATCH"),
241 "ROLE_PERMISSION": "roles:id:",
242 },
243 },
244 "vims": {
245 "METHODS": ("GET", "POST"),
246 "ROLE_PERMISSION": "vims:",
247 "<ID>": {
248 "METHODS": ("GET", "DELETE", "PATCH"),
249 "ROLE_PERMISSION": "vims:id:",
250 },
251 },
252 "vim_accounts": {
253 "METHODS": ("GET", "POST"),
254 "ROLE_PERMISSION": "vim_accounts:",
255 "<ID>": {
256 "METHODS": ("GET", "DELETE", "PATCH"),
257 "ROLE_PERMISSION": "vim_accounts:id:",
258 },
259 },
260 "wim_accounts": {
261 "METHODS": ("GET", "POST"),
262 "ROLE_PERMISSION": "wim_accounts:",
263 "<ID>": {
264 "METHODS": ("GET", "DELETE", "PATCH"),
265 "ROLE_PERMISSION": "wim_accounts:id:",
266 },
267 },
268 "sdns": {
269 "METHODS": ("GET", "POST"),
270 "ROLE_PERMISSION": "sdn_controllers:",
271 "<ID>": {
272 "METHODS": ("GET", "DELETE", "PATCH"),
273 "ROLE_PERMISSION": "sdn_controllers:id:",
274 },
275 },
276 "k8sclusters": {
277 "METHODS": ("GET", "POST"),
278 "ROLE_PERMISSION": "k8sclusters:",
279 "<ID>": {
280 "METHODS": ("GET", "DELETE", "PATCH"),
281 "ROLE_PERMISSION": "k8sclusters:id:",
282 },
283 },
284 "vca": {
285 "METHODS": ("GET", "POST"),
286 "ROLE_PERMISSION": "vca:",
287 "<ID>": {
288 "METHODS": ("GET", "DELETE", "PATCH"),
289 "ROLE_PERMISSION": "vca:id:",
290 },
291 },
292 "k8srepos": {
293 "METHODS": ("GET", "POST"),
294 "ROLE_PERMISSION": "k8srepos:",
295 "<ID>": {
296 "METHODS": ("GET", "DELETE"),
297 "ROLE_PERMISSION": "k8srepos:id:",
298 },
299 },
300 "osmrepos": {
301 "METHODS": ("GET", "POST"),
302 "ROLE_PERMISSION": "osmrepos:",
303 "<ID>": {
304 "METHODS": ("GET", "DELETE", "PATCH"),
305 "ROLE_PERMISSION": "osmrepos:id:",
306 },
307 },
308 "domains": {
309 "METHODS": ("GET",),
310 "ROLE_PERMISSION": "domains:",
311 },
tierno701018c2019-06-25 11:13:14 +0000312 }
313 },
314 "pdu": {
315 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100316 "pdu_descriptors": {
317 "METHODS": ("GET", "POST"),
318 "ROLE_PERMISSION": "pduds:",
319 "<ID>": {
320 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
321 "ROLE_PERMISSION": "pduds:id:",
322 },
323 },
tierno701018c2019-06-25 11:13:14 +0000324 }
325 },
326 "nsd": {
327 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100328 "ns_descriptors_content": {
329 "METHODS": ("GET", "POST"),
330 "ROLE_PERMISSION": "nsds:",
331 "<ID>": {
332 "METHODS": ("GET", "PUT", "DELETE"),
333 "ROLE_PERMISSION": "nsds:id:",
334 },
335 },
336 "ns_descriptors": {
337 "METHODS": ("GET", "POST"),
338 "ROLE_PERMISSION": "nsds:",
339 "<ID>": {
340 "METHODS": ("GET", "DELETE", "PATCH"),
341 "ROLE_PERMISSION": "nsds:id:",
342 "nsd_content": {
343 "METHODS": ("GET", "PUT"),
344 "ROLE_PERMISSION": "nsds:id:content:",
345 },
346 "nsd": {
347 "METHODS": ("GET",), # descriptor inside package
348 "ROLE_PERMISSION": "nsds:id:content:",
349 },
350 "artifacts": {
351 "METHODS": ("GET",),
352 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
353 "*": None,
354 },
355 },
356 },
357 "pnf_descriptors": {
358 "TODO": ("GET", "POST"),
359 "<ID>": {
360 "TODO": ("GET", "DELETE", "PATCH"),
361 "pnfd_content": {"TODO": ("GET", "PUT")},
362 },
363 },
364 "subscriptions": {
365 "TODO": ("GET", "POST"),
366 "<ID>": {"TODO": ("GET", "DELETE")},
367 },
tierno701018c2019-06-25 11:13:14 +0000368 }
369 },
370 "vnfpkgm": {
371 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100372 "vnf_packages_content": {
373 "METHODS": ("GET", "POST"),
374 "ROLE_PERMISSION": "vnfds:",
375 "<ID>": {
376 "METHODS": ("GET", "PUT", "DELETE"),
377 "ROLE_PERMISSION": "vnfds:id:",
378 },
379 },
380 "vnf_packages": {
381 "METHODS": ("GET", "POST"),
382 "ROLE_PERMISSION": "vnfds:",
383 "<ID>": {
384 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
385 "ROLE_PERMISSION": "vnfds:id:",
386 "package_content": {
387 "METHODS": ("GET", "PUT"), # package
388 "ROLE_PERMISSION": "vnfds:id:",
389 "upload_from_uri": {
390 "METHODS": (),
391 "TODO": ("POST",),
392 "ROLE_PERMISSION": "vnfds:id:upload:",
393 },
394 },
395 "vnfd": {
396 "METHODS": ("GET",), # descriptor inside package
397 "ROLE_PERMISSION": "vnfds:id:content:",
398 },
399 "artifacts": {
400 "METHODS": ("GET",),
401 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
402 "*": None,
403 },
404 "action": {
405 "METHODS": ("POST",),
406 "ROLE_PERMISSION": "vnfds:id:action:",
407 },
408 },
409 },
410 "subscriptions": {
411 "TODO": ("GET", "POST"),
412 "<ID>": {"TODO": ("GET", "DELETE")},
413 },
414 "vnfpkg_op_occs": {
415 "METHODS": ("GET",),
416 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
417 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"},
418 },
tierno701018c2019-06-25 11:13:14 +0000419 }
420 },
421 "nslcm": {
422 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100423 "ns_instances_content": {
424 "METHODS": ("GET", "POST"),
425 "ROLE_PERMISSION": "ns_instances:",
426 "<ID>": {
427 "METHODS": ("GET", "DELETE"),
428 "ROLE_PERMISSION": "ns_instances:id:",
429 },
430 },
431 "ns_instances": {
432 "METHODS": ("GET", "POST"),
433 "ROLE_PERMISSION": "ns_instances:",
434 "<ID>": {
435 "METHODS": ("GET", "DELETE"),
436 "ROLE_PERMISSION": "ns_instances:id:",
garciadeblas0964edf2022-02-11 00:43:44 +0100437 "heal": {
438 "METHODS": ("POST",),
439 "ROLE_PERMISSION": "ns_instances:id:heal:",
440 },
garciadeblas4568a372021-03-24 09:19:48 +0100441 "scale": {
442 "METHODS": ("POST",),
443 "ROLE_PERMISSION": "ns_instances:id:scale:",
444 },
445 "terminate": {
446 "METHODS": ("POST",),
447 "ROLE_PERMISSION": "ns_instances:id:terminate:",
448 },
449 "instantiate": {
450 "METHODS": ("POST",),
451 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
452 },
elumalai8e3806c2022-04-28 17:26:24 +0530453 "migrate": {
454 "METHODS": ("POST",),
455 "ROLE_PERMISSION": "ns_instances:id:migrate:",
456 },
garciadeblas4568a372021-03-24 09:19:48 +0100457 "action": {
458 "METHODS": ("POST",),
459 "ROLE_PERMISSION": "ns_instances:id:action:",
460 },
aticig544a2ae2022-04-05 09:00:17 +0300461 "update": {
462 "METHODS": ("POST",),
463 "ROLE_PERMISSION": "ns_instances:id:update:",
464 },
govindarajul519da482022-04-29 19:05:22 +0530465 "verticalscale": {
466 "METHODS": ("POST",),
garciadeblasf2af4a12023-01-24 16:56:54 +0100467 "ROLE_PERMISSION": "ns_instances:id:verticalscale:",
468 },
garciadeblas4568a372021-03-24 09:19:48 +0100469 },
470 },
471 "ns_lcm_op_occs": {
472 "METHODS": ("GET",),
473 "ROLE_PERMISSION": "ns_instances:opps:",
474 "<ID>": {
475 "METHODS": ("GET",),
476 "ROLE_PERMISSION": "ns_instances:opps:id:",
477 },
478 },
479 "vnfrs": {
480 "METHODS": ("GET",),
481 "ROLE_PERMISSION": "vnf_instances:",
482 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
483 },
484 "vnf_instances": {
485 "METHODS": ("GET",),
486 "ROLE_PERMISSION": "vnf_instances:",
487 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
488 },
489 "subscriptions": {
490 "METHODS": ("GET", "POST"),
491 "ROLE_PERMISSION": "ns_subscriptions:",
492 "<ID>": {
493 "METHODS": ("GET", "DELETE"),
494 "ROLE_PERMISSION": "ns_subscriptions:id:",
495 },
496 },
tierno701018c2019-06-25 11:13:14 +0000497 }
498 },
almagiae47b9132022-05-17 14:12:22 +0200499 "vnflcm": {
500 "v1": {
garciadeblasf2af4a12023-01-24 16:56:54 +0100501 "vnf_instances": {
502 "METHODS": ("GET", "POST"),
503 "ROLE_PERMISSION": "vnflcm_instances:",
504 "<ID>": {
505 "METHODS": ("GET", "DELETE"),
506 "ROLE_PERMISSION": "vnflcm_instances:id:",
507 "scale": {
508 "METHODS": ("POST",),
509 "ROLE_PERMISSION": "vnflcm_instances:id:scale:",
510 },
511 "terminate": {
512 "METHODS": ("POST",),
513 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:",
514 },
515 "instantiate": {
516 "METHODS": ("POST",),
517 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:",
518 },
519 },
520 },
521 "vnf_lcm_op_occs": {
522 "METHODS": ("GET",),
523 "ROLE_PERMISSION": "vnf_instances:opps:",
524 "<ID>": {
525 "METHODS": ("GET",),
526 "ROLE_PERMISSION": "vnf_instances:opps:id:",
527 },
528 },
529 "subscriptions": {
530 "METHODS": ("GET", "POST"),
531 "ROLE_PERMISSION": "vnflcm_subscriptions:",
532 "<ID>": {
533 "METHODS": ("GET", "DELETE"),
534 "ROLE_PERMISSION": "vnflcm_subscriptions:id:",
535 },
536 },
almagiae47b9132022-05-17 14:12:22 +0200537 }
538 },
tierno701018c2019-06-25 11:13:14 +0000539 "nst": {
540 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100541 "netslice_templates_content": {
542 "METHODS": ("GET", "POST"),
543 "ROLE_PERMISSION": "slice_templates:",
544 "<ID>": {
545 "METHODS": ("GET", "PUT", "DELETE"),
546 "ROLE_PERMISSION": "slice_templates:id:",
547 },
548 },
549 "netslice_templates": {
550 "METHODS": ("GET", "POST"),
551 "ROLE_PERMISSION": "slice_templates:",
552 "<ID>": {
553 "METHODS": ("GET", "DELETE"),
554 "TODO": ("PATCH",),
555 "ROLE_PERMISSION": "slice_templates:id:",
556 "nst_content": {
557 "METHODS": ("GET", "PUT"),
558 "ROLE_PERMISSION": "slice_templates:id:content:",
559 },
560 "nst": {
561 "METHODS": ("GET",), # descriptor inside package
562 "ROLE_PERMISSION": "slice_templates:id:content:",
563 },
564 "artifacts": {
565 "METHODS": ("GET",),
566 "ROLE_PERMISSION": "slice_templates:id:content:",
567 "*": None,
568 },
569 },
570 },
571 "subscriptions": {
572 "TODO": ("GET", "POST"),
573 "<ID>": {"TODO": ("GET", "DELETE")},
574 },
tierno701018c2019-06-25 11:13:14 +0000575 }
576 },
577 "nsilcm": {
578 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100579 "netslice_instances_content": {
580 "METHODS": ("GET", "POST"),
581 "ROLE_PERMISSION": "slice_instances:",
582 "<ID>": {
583 "METHODS": ("GET", "DELETE"),
584 "ROLE_PERMISSION": "slice_instances:id:",
585 },
586 },
587 "netslice_instances": {
588 "METHODS": ("GET", "POST"),
589 "ROLE_PERMISSION": "slice_instances:",
590 "<ID>": {
591 "METHODS": ("GET", "DELETE"),
592 "ROLE_PERMISSION": "slice_instances:id:",
593 "terminate": {
594 "METHODS": ("POST",),
595 "ROLE_PERMISSION": "slice_instances:id:terminate:",
596 },
597 "instantiate": {
598 "METHODS": ("POST",),
599 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
600 },
601 "action": {
602 "METHODS": ("POST",),
603 "ROLE_PERMISSION": "slice_instances:id:action:",
604 },
605 },
606 },
607 "nsi_lcm_op_occs": {
608 "METHODS": ("GET",),
609 "ROLE_PERMISSION": "slice_instances:opps:",
610 "<ID>": {
611 "METHODS": ("GET",),
612 "ROLE_PERMISSION": "slice_instances:opps:id:",
613 },
614 },
tierno701018c2019-06-25 11:13:14 +0000615 }
616 },
617 "nspm": {
618 "v1": {
619 "pm_jobs": {
620 "<ID>": {
621 "reports": {
garciadeblas4568a372021-03-24 09:19:48 +0100622 "<ID>": {
623 "METHODS": ("GET",),
624 "ROLE_PERMISSION": "reports:id:",
625 }
tierno701018c2019-06-25 11:13:14 +0000626 }
627 },
628 },
629 },
630 },
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000631 "nsfm": {
632 "v1": {
garciadeblasf2af4a12023-01-24 16:56:54 +0100633 "alarms": {
634 "METHODS": ("GET", "PATCH"),
635 "ROLE_PERMISSION": "alarms:",
636 "<ID>": {
637 "METHODS": ("GET", "PATCH"),
638 "ROLE_PERMISSION": "alarms:id:",
639 },
640 }
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000641 },
642 },
tierno701018c2019-06-25 11:13:14 +0000643}
644
tiernoc94c3df2018-02-09 15:38:54 +0100645
646class NbiException(Exception):
tiernoc94c3df2018-02-09 15:38:54 +0100647 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
648 Exception.__init__(self, message)
649 self.http_code = http_code
650
651
652class Server(object):
653 instance = 0
654 # to decode bytes to str
655 reader = getreader("utf-8")
656
657 def __init__(self):
658 self.instance += 1
tierno701018c2019-06-25 11:13:14 +0000659 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
delacruzramoad682a52019-12-10 16:26:34 +0100660 self.engine = Engine(self.authenticator)
tiernoc94c3df2018-02-09 15:38:54 +0100661
tiernoc94c3df2018-02-09 15:38:54 +0100662 def _format_in(self, kwargs):
garciadeblasf2af4a12023-01-24 16:56:54 +0100663 error_text = "" # error_text must be initialized outside try
tiernoc94c3df2018-02-09 15:38:54 +0100664 try:
665 indata = None
666 if cherrypy.request.body.length:
667 error_text = "Invalid input format "
668
669 if "Content-Type" in cherrypy.request.headers:
670 if "application/json" in cherrypy.request.headers["Content-Type"]:
671 error_text = "Invalid json format "
672 indata = json.load(self.reader(cherrypy.request.body))
gcalvinode4adfe2018-10-30 11:46:09 +0100673 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100674 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
675 error_text = "Invalid yaml format "
garciadeblas4568a372021-03-24 09:19:48 +0100676 indata = yaml.load(
677 cherrypy.request.body, Loader=yaml.SafeLoader
678 )
gcalvinode4adfe2018-10-30 11:46:09 +0100679 cherrypy.request.headers.pop("Content-File-MD5", None)
garciadeblas4568a372021-03-24 09:19:48 +0100680 elif (
681 "application/binary" in cherrypy.request.headers["Content-Type"]
682 or "application/gzip"
683 in cherrypy.request.headers["Content-Type"]
684 or "application/zip" in cherrypy.request.headers["Content-Type"]
685 or "text/plain" in cherrypy.request.headers["Content-Type"]
686 ):
tiernof27c79b2018-03-12 17:08:42 +0100687 indata = cherrypy.request.body # .read()
garciadeblas4568a372021-03-24 09:19:48 +0100688 elif (
689 "multipart/form-data"
690 in cherrypy.request.headers["Content-Type"]
691 ):
tiernoc94c3df2018-02-09 15:38:54 +0100692 if "descriptor_file" in kwargs:
693 filecontent = kwargs.pop("descriptor_file")
694 if not filecontent.file:
garciadeblas4568a372021-03-24 09:19:48 +0100695 raise NbiException(
696 "empty file or content", HTTPStatus.BAD_REQUEST
697 )
tiernof27c79b2018-03-12 17:08:42 +0100698 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100699 if filecontent.content_type.value:
garciadeblas4568a372021-03-24 09:19:48 +0100700 cherrypy.request.headers[
701 "Content-Type"
702 ] = filecontent.content_type.value
tiernoc94c3df2018-02-09 15:38:54 +0100703 else:
704 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
705 # "Only 'Content-Type' of type 'application/json' or
706 # 'application/yaml' for input format are available")
707 error_text = "Invalid yaml format "
garciadeblas4568a372021-03-24 09:19:48 +0100708 indata = yaml.load(
709 cherrypy.request.body, Loader=yaml.SafeLoader
710 )
gcalvinode4adfe2018-10-30 11:46:09 +0100711 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100712 else:
713 error_text = "Invalid yaml format "
delacruzramob19cadc2019-10-08 10:18:02 +0200714 indata = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
gcalvinode4adfe2018-10-30 11:46:09 +0100715 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100716 if not indata:
717 indata = {}
718
tiernoc94c3df2018-02-09 15:38:54 +0100719 format_yaml = False
720 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
721 format_yaml = True
722
723 for k, v in kwargs.items():
724 if isinstance(v, str):
725 if v == "":
726 kwargs[k] = None
727 elif format_yaml:
728 try:
delacruzramob19cadc2019-10-08 10:18:02 +0200729 kwargs[k] = yaml.load(v, Loader=yaml.SafeLoader)
tiernoe1281182018-05-22 12:24:36 +0200730 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100731 pass
garciadeblas4568a372021-03-24 09:19:48 +0100732 elif (
733 k.endswith(".gt")
734 or k.endswith(".lt")
735 or k.endswith(".gte")
736 or k.endswith(".lte")
737 ):
tiernoc94c3df2018-02-09 15:38:54 +0100738 try:
739 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200740 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100741 try:
742 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200743 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100744 pass
745 elif v.find(",") > 0:
746 kwargs[k] = v.split(",")
747 elif isinstance(v, (list, tuple)):
748 for index in range(0, len(v)):
749 if v[index] == "":
750 v[index] = None
751 elif format_yaml:
752 try:
delacruzramob19cadc2019-10-08 10:18:02 +0200753 v[index] = yaml.load(v[index], Loader=yaml.SafeLoader)
tiernoe1281182018-05-22 12:24:36 +0200754 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100755 pass
756
tiernof27c79b2018-03-12 17:08:42 +0100757 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100758 except (ValueError, yaml.YAMLError) as exc:
759 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
760 except KeyError as exc:
garciadeblas4568a372021-03-24 09:19:48 +0100761 raise NbiException(
762 "Query string error: " + str(exc), HTTPStatus.BAD_REQUEST
763 )
tiernob92094f2018-05-11 13:44:22 +0200764 except Exception as exc:
765 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100766
767 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000768 def _format_out(data, token_info=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100769 """
770 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100771 :param data: response to be sent. Can be a dict, text or file
tierno701018c2019-06-25 11:13:14 +0000772 :param token_info: Contains among other username and project
tierno5792d7d2019-08-30 15:37:12 +0000773 :param _format: The format to be set as Content-Type if data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100774 :return: None
775 """
tierno0f98af52018-03-19 10:28:22 +0100776 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100777 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100778 if accept and "text/html" in accept:
garciadeblas4568a372021-03-24 09:19:48 +0100779 return html.format(
780 data, cherrypy.request, cherrypy.response, token_info
781 )
tierno09c073e2018-04-26 13:36:48 +0200782 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100783 return
784 elif hasattr(data, "read"): # file object
785 if _format:
786 cherrypy.response.headers["Content-Type"] = _format
787 elif "b" in data.mode: # binariy asssumig zip
garciadeblas4568a372021-03-24 09:19:48 +0100788 cherrypy.response.headers["Content-Type"] = "application/zip"
tiernof27c79b2018-03-12 17:08:42 +0100789 else:
garciadeblas4568a372021-03-24 09:19:48 +0100790 cherrypy.response.headers["Content-Type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100791 # TODO check that cherrypy close file. If not implement pending things to close per thread next
792 return data
tierno0f98af52018-03-19 10:28:22 +0100793 if accept:
Frank Bryden02e700c2020-06-03 13:34:16 +0000794 if "text/html" in accept:
garciadeblas4568a372021-03-24 09:19:48 +0100795 return html.format(
796 data, cherrypy.request, cherrypy.response, token_info
797 )
Frank Brydenb5422da2020-08-10 11:44:11 +0000798 elif "application/yaml" in accept or "*/*" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100799 pass
garciadeblas4568a372021-03-24 09:19:48 +0100800 elif "application/json" in accept or (
801 cherrypy.response.status and cherrypy.response.status >= 300
802 ):
803 cherrypy.response.headers[
804 "Content-Type"
805 ] = "application/json; charset=utf-8"
Frank Bryden02e700c2020-06-03 13:34:16 +0000806 a = json.dumps(data, indent=4) + "\n"
garciadeblas4568a372021-03-24 09:19:48 +0100807 return a.encode("utf8")
808 cherrypy.response.headers["Content-Type"] = "application/yaml"
809 return yaml.safe_dump(
810 data,
811 explicit_start=True,
812 indent=4,
813 default_flow_style=False,
814 tags=False,
815 encoding="utf-8",
816 allow_unicode=True,
817 ) # , canonical=True, default_style='"'
tiernoc94c3df2018-02-09 15:38:54 +0100818
819 @cherrypy.expose
820 def index(self, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000821 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100822 try:
823 if cherrypy.request.method == "GET":
tierno701018c2019-06-25 11:13:14 +0000824 token_info = self.authenticator.authorize()
garciadeblas4568a372021-03-24 09:19:48 +0100825 outdata = token_info # Home page
tiernoc94c3df2018-02-09 15:38:54 +0100826 else:
garciadeblas4568a372021-03-24 09:19:48 +0100827 raise cherrypy.HTTPError(
828 HTTPStatus.METHOD_NOT_ALLOWED.value,
829 "Method {} not allowed for tokens".format(cherrypy.request.method),
830 )
tiernoc94c3df2018-02-09 15:38:54 +0100831
tierno701018c2019-06-25 11:13:14 +0000832 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100833
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100834 except (EngineException, AuthException) as e:
tierno701018c2019-06-25 11:13:14 +0000835 # cherrypy.log("index Exception {}".format(e))
tiernoc94c3df2018-02-09 15:38:54 +0100836 cherrypy.response.status = e.http_code.value
tierno701018c2019-06-25 11:13:14 +0000837 return self._format_out("Welcome to OSM!", token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100838
839 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200840 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200841 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200842 try:
843 if cherrypy.request.method != "GET":
garciadeblas4568a372021-03-24 09:19:48 +0100844 raise NbiException(
845 "Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED
846 )
tierno55945e72018-04-06 16:40:27 +0200847 elif args or kwargs:
garciadeblas4568a372021-03-24 09:19:48 +0100848 raise NbiException(
849 "Invalid URL or query string for version",
850 HTTPStatus.METHOD_NOT_ALLOWED,
851 )
tierno9c630112019-08-29 14:21:41 +0000852 # TODO include version of other modules, pick up from some kafka admin message
tierno5792d7d2019-08-30 15:37:12 +0000853 osm_nbi_version = {"version": nbi_version, "date": nbi_version_date}
854 return self._format_out(osm_nbi_version)
tierno55945e72018-04-06 16:40:27 +0200855 except NbiException as e:
856 cherrypy.response.status = e.http_code.value
857 problem_details = {
858 "code": e.http_code.name,
859 "status": e.http_code.value,
860 "detail": str(e),
861 }
862 return self._format_out(problem_details, None)
863
tierno12eac3c2020-03-19 23:22:08 +0000864 def domain(self):
865 try:
866 domains = {
garciadeblas4568a372021-03-24 09:19:48 +0100867 "user_domain_name": cherrypy.tree.apps["/osm"]
868 .config["authentication"]
869 .get("user_domain_name"),
870 "project_domain_name": cherrypy.tree.apps["/osm"]
871 .config["authentication"]
872 .get("project_domain_name"),
873 }
tierno12eac3c2020-03-19 23:22:08 +0000874 return self._format_out(domains)
875 except NbiException as e:
876 cherrypy.response.status = e.http_code.value
877 problem_details = {
878 "code": e.http_code.name,
879 "status": e.http_code.value,
880 "detail": str(e),
881 }
882 return self._format_out(problem_details, None)
883
tiernoa5035702019-07-29 08:54:42 +0000884 @staticmethod
885 def _format_login(token_info):
886 """
887 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
888 log this information
889 :param token_info: Dictionary with token content
890 :return: None
891 """
892 cherrypy.request.login = token_info.get("username", "-")
893 if token_info.get("project_name"):
894 cherrypy.request.login += "/" + token_info["project_name"]
895 if token_info.get("id"):
896 cherrypy.request.login += ";session=" + token_info["id"][0:12]
897
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000898 # NS Fault Management
899 @cherrypy.expose
garciadeblasf2af4a12023-01-24 16:56:54 +0100900 def nsfm(
901 self,
902 version=None,
903 topic=None,
904 uuid=None,
905 project_name=None,
906 ns_id=None,
907 *args,
Patricia Reinoso62fa6732023-02-22 17:57:53 +0000908 **kwargs,
garciadeblasf2af4a12023-01-24 16:56:54 +0100909 ):
910 if topic == "alarms":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000911 try:
912 method = cherrypy.request.method
garciadeblasf2af4a12023-01-24 16:56:54 +0100913 role_permission = self._check_valid_url_method(
914 method, "nsfm", version, topic, None, None, *args
915 )
916 query_string_operations = self._extract_query_string_operations(
917 kwargs, method
918 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000919
garciadeblasf2af4a12023-01-24 16:56:54 +0100920 self.authenticator.authorize(
921 role_permission, query_string_operations, None
922 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000923
924 # to handle get request
garciadeblasf2af4a12023-01-24 16:56:54 +0100925 if cherrypy.request.method == "GET":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000926 # if request is on basis of uuid
garciadeblasf2af4a12023-01-24 16:56:54 +0100927 if uuid and uuid != "None":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000928 try:
929 alarm = self.engine.db.get_one("alarms", {"uuid": uuid})
garciadeblasf2af4a12023-01-24 16:56:54 +0100930 alarm_action = self.engine.db.get_one(
931 "alarms_action", {"uuid": uuid}
932 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000933 alarm.update(alarm_action)
garciadeblasf2af4a12023-01-24 16:56:54 +0100934 vnf = self.engine.db.get_one(
935 "vnfrs", {"nsr-id-ref": alarm["tags"]["ns_id"]}
936 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000937 alarm["vnf-id"] = vnf["_id"]
938 return self._format_out(str(alarm))
939 except Exception:
940 return self._format_out("Please provide valid alarm uuid")
garciadeblasf2af4a12023-01-24 16:56:54 +0100941 elif ns_id and ns_id != "None":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000942 # if request is on basis of ns_id
943 try:
garciadeblasf2af4a12023-01-24 16:56:54 +0100944 alarms = self.engine.db.get_list(
945 "alarms", {"tags.ns_id": ns_id}
946 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000947 for alarm in alarms:
garciadeblasf2af4a12023-01-24 16:56:54 +0100948 alarm_action = self.engine.db.get_one(
949 "alarms_action", {"uuid": alarm["uuid"]}
950 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000951 alarm.update(alarm_action)
952 return self._format_out(str(alarms))
953 except Exception:
954 return self._format_out("Please provide valid ns id")
955 else:
956 # to return only alarm which are related to given project
garciadeblasf2af4a12023-01-24 16:56:54 +0100957 project = self.engine.db.get_one(
958 "projects", {"name": project_name}
959 )
960 project_id = project.get("_id")
961 ns_list = self.engine.db.get_list(
962 "nsrs", {"_admin.projects_read": project_id}
963 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000964 ns_ids = []
965 for ns in ns_list:
966 ns_ids.append(ns.get("_id"))
967 alarms = self.engine.db.get_list("alarms")
garciadeblasf2af4a12023-01-24 16:56:54 +0100968 alarm_list = [
969 alarm
970 for alarm in alarms
971 if alarm["tags"]["ns_id"] in ns_ids
972 ]
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000973 for alrm in alarm_list:
garciadeblasf2af4a12023-01-24 16:56:54 +0100974 action = self.engine.db.get_one(
975 "alarms_action", {"uuid": alrm.get("uuid")}
976 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000977 alrm.update(action)
978 return self._format_out(str(alarm_list))
979 # to handle patch request for alarm update
garciadeblasf2af4a12023-01-24 16:56:54 +0100980 elif cherrypy.request.method == "PATCH":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000981 data = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
982 try:
983 # check if uuid is valid
984 self.engine.db.get_one("alarms", {"uuid": data.get("uuid")})
985 except Exception:
986 return self._format_out("Please provide valid alarm uuid.")
987 if data.get("is_enable") is not None:
988 if data.get("is_enable"):
garciadeblasf2af4a12023-01-24 16:56:54 +0100989 alarm_status = "ok"
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000990 else:
garciadeblasf2af4a12023-01-24 16:56:54 +0100991 alarm_status = "disabled"
992 self.engine.db.set_one(
993 "alarms",
994 {"uuid": data.get("uuid")},
995 {"alarm_status": alarm_status},
996 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000997 else:
garciadeblasf2af4a12023-01-24 16:56:54 +0100998 self.engine.db.set_one(
999 "alarms",
1000 {"uuid": data.get("uuid")},
1001 {"threshold": data.get("threshold")},
1002 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001003 return self._format_out("Alarm updated")
1004 except Exception as e:
garciadeblasf2af4a12023-01-24 16:56:54 +01001005 if isinstance(
1006 e,
1007 (
1008 NbiException,
1009 EngineException,
1010 DbException,
1011 FsException,
1012 MsgException,
1013 AuthException,
1014 ValidationError,
1015 AuthconnException,
1016 ),
1017 ):
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001018 http_code_value = cherrypy.response.status = e.http_code.value
1019 http_code_name = e.http_code.name
1020 cherrypy.log("Exception {}".format(e))
1021 else:
garciadeblasf2af4a12023-01-24 16:56:54 +01001022 http_code_value = (
1023 cherrypy.response.status
1024 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001025 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
1026 http_code_name = HTTPStatus.BAD_REQUEST.name
1027 problem_details = {
1028 "code": http_code_name,
1029 "status": http_code_value,
1030 "detail": str(e),
1031 }
1032 return self._format_out(problem_details)
1033
tierno55945e72018-04-06 16:40:27 +02001034 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +01001035 def token(self, method, token_id=None, kwargs=None):
tierno701018c2019-06-25 11:13:14 +00001036 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +01001037 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +01001038 indata = self._format_in(kwargs)
1039 if not isinstance(indata, dict):
garciadeblas4568a372021-03-24 09:19:48 +01001040 raise NbiException(
1041 "Expected application/yaml or application/json Content-Type",
1042 HTTPStatus.BAD_REQUEST,
1043 )
tiernoa5035702019-07-29 08:54:42 +00001044
1045 if method == "GET":
1046 token_info = self.authenticator.authorize()
1047 # for logging
1048 self._format_login(token_info)
1049 if token_id:
1050 outdata = self.authenticator.get_token(token_info, token_id)
tiernoc94c3df2018-02-09 15:38:54 +01001051 else:
tiernoa5035702019-07-29 08:54:42 +00001052 outdata = self.authenticator.get_token_list(token_info)
1053 elif method == "POST":
1054 try:
1055 token_info = self.authenticator.authorize()
1056 except Exception:
1057 token_info = None
1058 if kwargs:
1059 indata.update(kwargs)
1060 # This is needed to log the user when authentication fails
1061 cherrypy.request.login = "{}".format(indata.get("username", "-"))
garciadeblas4568a372021-03-24 09:19:48 +01001062 outdata = token_info = self.authenticator.new_token(
1063 token_info, indata, cherrypy.request.remote
1064 )
garciadeblasf2af4a12023-01-24 16:56:54 +01001065 cherrypy.session["Authorization"] = outdata["_id"] # pylint: disable=E1101
tiernoa5035702019-07-29 08:54:42 +00001066 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
1067 # for logging
1068 self._format_login(token_info)
selvi.ja9a1fc82022-04-04 06:54:30 +00001069 # password expiry check
1070 if self.authenticator.check_password_expiry(outdata):
garciadeblasf2af4a12023-01-24 16:56:54 +01001071 outdata = {
1072 "id": outdata["id"],
1073 "message": "change_password",
1074 "user_id": outdata["user_id"],
1075 }
tiernoa5035702019-07-29 08:54:42 +00001076 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1077 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1078 elif method == "DELETE":
1079 if not token_id and "id" in kwargs:
1080 token_id = kwargs["id"]
1081 elif not token_id:
1082 token_info = self.authenticator.authorize()
1083 # for logging
1084 self._format_login(token_info)
1085 token_id = token_info["_id"]
1086 outdata = self.authenticator.del_token(token_id)
1087 token_info = None
garciadeblasf2af4a12023-01-24 16:56:54 +01001088 cherrypy.session["Authorization"] = "logout" # pylint: disable=E1101
tiernoa5035702019-07-29 08:54:42 +00001089 # cherrypy.response.cookie["Authorization"] = token_id
1090 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1091 else:
garciadeblas4568a372021-03-24 09:19:48 +01001092 raise NbiException(
1093 "Method {} not allowed for token".format(method),
1094 HTTPStatus.METHOD_NOT_ALLOWED,
1095 )
tiernoa5035702019-07-29 08:54:42 +00001096 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001097
1098 @cherrypy.expose
1099 def test(self, *args, **kwargs):
garciadeblas4568a372021-03-24 09:19:48 +01001100 if not cherrypy.config.get("server.enable_test") or (
1101 isinstance(cherrypy.config["server.enable_test"], str)
1102 and cherrypy.config["server.enable_test"].lower() == "false"
1103 ):
tierno4836bac2020-01-15 14:41:48 +00001104 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value
1105 return "test URL is disabled"
tiernoc94c3df2018-02-09 15:38:54 +01001106 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +01001107 if args and args[0] == "help":
garciadeblas4568a372021-03-24 09:19:48 +01001108 return (
1109 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1110 "sleep/<time>\nmessage/topic\n</pre></html>"
1111 )
tiernof27c79b2018-03-12 17:08:42 +01001112
1113 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +01001114 try:
1115 # self.engine.load_dbase(cherrypy.request.app.config)
garciadeblasf2af4a12023-01-24 16:56:54 +01001116 pid = self.authenticator.create_admin_project()
1117 self.authenticator.create_admin_user(pid)
tiernoc94c3df2018-02-09 15:38:54 +01001118 return "Done. User 'admin', password 'admin' created"
1119 except Exception:
1120 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1121 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +01001122 elif args and args[0] == "file":
garciadeblas4568a372021-03-24 09:19:48 +01001123 return cherrypy.lib.static.serve_file(
1124 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1],
1125 "text/plain",
1126 "attachment",
1127 )
tiernof27c79b2018-03-12 17:08:42 +01001128 elif args and args[0] == "file2":
garciadeblas4568a372021-03-24 09:19:48 +01001129 f_path = (
1130 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1]
1131 )
tiernof27c79b2018-03-12 17:08:42 +01001132 f = open(f_path, "r")
1133 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +01001134 return f
tierno55945e72018-04-06 16:40:27 +02001135
tiernof27c79b2018-03-12 17:08:42 +01001136 elif len(args) == 2 and args[0] == "db-clear":
tiernof717cbe2018-12-03 16:35:42 +00001137 deleted_info = self.engine.db.del_list(args[1], kwargs)
1138 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
1139 elif len(args) and args[0] == "fs-clear":
1140 if len(args) >= 2:
1141 folders = (args[1],)
1142 else:
1143 folders = self.engine.fs.dir_ls(".")
1144 for folder in folders:
1145 self.engine.fs.file_delete(folder)
1146 return ",".join(folders) + " folders deleted\n"
tiernoc94c3df2018-02-09 15:38:54 +01001147 elif args and args[0] == "login":
1148 if not cherrypy.request.headers.get("Authorization"):
garciadeblas4568a372021-03-24 09:19:48 +01001149 cherrypy.response.headers[
1150 "WWW-Authenticate"
1151 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
tiernoc94c3df2018-02-09 15:38:54 +01001152 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1153 elif args and args[0] == "login2":
1154 if not cherrypy.request.headers.get("Authorization"):
garciadeblas4568a372021-03-24 09:19:48 +01001155 cherrypy.response.headers[
1156 "WWW-Authenticate"
1157 ] = 'Bearer realm="Access to OSM site"'
tiernoc94c3df2018-02-09 15:38:54 +01001158 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1159 elif args and args[0] == "sleep":
1160 sleep_time = 5
1161 try:
1162 sleep_time = int(args[1])
1163 except Exception:
1164 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1165 return self._format_out("Database already initialized")
1166 thread_info = cherrypy.thread_data
1167 print(thread_info)
1168 time.sleep(sleep_time)
1169 # thread_info
1170 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +02001171 main_topic = args[1]
1172 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +01001173 try:
garciadeblas4568a372021-03-24 09:19:48 +01001174 if cherrypy.request.method == "POST":
delacruzramob19cadc2019-10-08 10:18:02 +02001175 to_send = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
tierno55945e72018-04-06 16:40:27 +02001176 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +02001177 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +02001178 return_text += " {}: {}\n".format(k, v)
garciadeblas4568a372021-03-24 09:19:48 +01001179 elif cherrypy.request.method == "GET":
tierno55945e72018-04-06 16:40:27 +02001180 for k, v in kwargs.items():
tiernof1509b22020-05-12 14:32:37 +00001181 v_dict = yaml.load(v, Loader=yaml.SafeLoader)
1182 self.engine.msg.write(main_topic, k, v_dict)
1183 return_text += " {}: {}\n".format(k, v_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001184 except Exception as e:
tierno55945e72018-04-06 16:40:27 +02001185 return_text += "Error: " + str(e)
1186 return_text += "</pre></html>\n"
1187 return return_text
tiernoc94c3df2018-02-09 15:38:54 +01001188
1189 return_text = (
garciadeblas4568a372021-03-24 09:19:48 +01001190 "<html><pre>\nheaders:\n args: {}\n".format(args)
1191 + " kwargs: {}\n".format(kwargs)
1192 + " headers: {}\n".format(cherrypy.request.headers)
1193 + " path_info: {}\n".format(cherrypy.request.path_info)
1194 + " query_string: {}\n".format(cherrypy.request.query_string)
garciadeblasf2af4a12023-01-24 16:56:54 +01001195 + " session: {}\n".format(cherrypy.session) # pylint: disable=E1101
garciadeblas4568a372021-03-24 09:19:48 +01001196 + " cookie: {}\n".format(cherrypy.request.cookie)
1197 + " method: {}\n".format(cherrypy.request.method)
garciadeblasf2af4a12023-01-24 16:56:54 +01001198 + " session: {}\n".format(
1199 cherrypy.session.get("fieldname") # pylint: disable=E1101
1200 )
garciadeblas4568a372021-03-24 09:19:48 +01001201 + " body:\n"
1202 )
tiernoc94c3df2018-02-09 15:38:54 +01001203 return_text += " length: {}\n".format(cherrypy.request.body.length)
1204 if cherrypy.request.body.length:
1205 return_text += " content: {}\n".format(
garciadeblas4568a372021-03-24 09:19:48 +01001206 str(
1207 cherrypy.request.body.read(
1208 int(cherrypy.request.headers.get("Content-Length", 0))
1209 )
1210 )
1211 )
tiernoc94c3df2018-02-09 15:38:54 +01001212 if thread_info:
1213 return_text += "thread: {}\n".format(thread_info)
1214 return_text += "</pre></html>"
1215 return return_text
1216
tierno701018c2019-06-25 11:13:14 +00001217 @staticmethod
1218 def _check_valid_url_method(method, *args):
tiernof27c79b2018-03-12 17:08:42 +01001219 if len(args) < 3:
garciadeblas4568a372021-03-24 09:19:48 +01001220 raise NbiException(
1221 "URL must contain at least 'main_topic/version/topic'",
1222 HTTPStatus.METHOD_NOT_ALLOWED,
1223 )
tiernof27c79b2018-03-12 17:08:42 +01001224
tierno701018c2019-06-25 11:13:14 +00001225 reference = valid_url_methods
tiernof27c79b2018-03-12 17:08:42 +01001226 for arg in args:
1227 if arg is None:
1228 break
1229 if not isinstance(reference, dict):
garciadeblas4568a372021-03-24 09:19:48 +01001230 raise NbiException(
1231 "URL contains unexpected extra items '{}'".format(arg),
1232 HTTPStatus.METHOD_NOT_ALLOWED,
1233 )
tiernof27c79b2018-03-12 17:08:42 +01001234
1235 if arg in reference:
1236 reference = reference[arg]
1237 elif "<ID>" in reference:
1238 reference = reference["<ID>"]
1239 elif "*" in reference:
tierno74b53582020-06-18 10:52:37 +00001240 # if there is content
1241 if reference["*"]:
1242 reference = reference["*"]
tiernof27c79b2018-03-12 17:08:42 +01001243 break
1244 else:
garciadeblas4568a372021-03-24 09:19:48 +01001245 raise NbiException(
1246 "Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED
1247 )
tiernof27c79b2018-03-12 17:08:42 +01001248 if "TODO" in reference and method in reference["TODO"]:
garciadeblas4568a372021-03-24 09:19:48 +01001249 raise NbiException(
1250 "Method {} not supported yet for this URL".format(method),
1251 HTTPStatus.NOT_IMPLEMENTED,
1252 )
tierno2236d202018-05-16 19:05:16 +02001253 elif "METHODS" in reference and method not in reference["METHODS"]:
garciadeblas4568a372021-03-24 09:19:48 +01001254 raise NbiException(
1255 "Method {} not supported for this URL".format(method),
1256 HTTPStatus.METHOD_NOT_ALLOWED,
1257 )
tierno701018c2019-06-25 11:13:14 +00001258 return reference["ROLE_PERMISSION"] + method.lower()
tiernof27c79b2018-03-12 17:08:42 +01001259
1260 @staticmethod
tiernob24258a2018-10-04 18:39:49 +02001261 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +01001262 """
1263 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +02001264 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +01001265 :param version:
tiernob24258a2018-10-04 18:39:49 +02001266 :param topic:
tiernof27c79b2018-03-12 17:08:42 +01001267 :param id:
1268 :return: None
1269 """
1270 # 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 +01001271 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(
1272 main_topic, version, topic, id
1273 )
tiernof27c79b2018-03-12 17:08:42 +01001274 return
1275
tierno65ca36d2019-02-12 19:27:52 +01001276 @staticmethod
tierno701018c2019-06-25 11:13:14 +00001277 def _extract_query_string_operations(kwargs, method):
1278 """
1279
1280 :param kwargs:
1281 :return:
1282 """
1283 query_string_operations = []
1284 if kwargs:
1285 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1286 if qs in kwargs and kwargs[qs].lower() != "false":
1287 query_string_operations.append(qs.lower() + ":" + method.lower())
1288 return query_string_operations
1289
1290 @staticmethod
1291 def _manage_admin_query(token_info, kwargs, method, _id):
tierno65ca36d2019-02-12 19:27:52 +01001292 """
1293 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1294 Check that users has rights to use them and returs the admin_query
tierno701018c2019-06-25 11:13:14 +00001295 :param token_info: token_info rights obtained by token
tierno65ca36d2019-02-12 19:27:52 +01001296 :param kwargs: query string input.
1297 :param method: http method: GET, POSST, PUT, ...
1298 :param _id:
1299 :return: admin_query dictionary with keys:
1300 public: True, False or None
1301 force: True or False
1302 project_id: tuple with projects used for accessing an element
1303 set_project: tuple with projects that a created element will belong to
1304 method: show, list, delete, write
1305 """
garciadeblas4568a372021-03-24 09:19:48 +01001306 admin_query = {
1307 "force": False,
1308 "project_id": (token_info["project_id"],),
1309 "username": token_info["username"],
1310 "admin": token_info["admin"],
1311 "public": None,
1312 "allow_show_user_project_role": token_info["allow_show_user_project_role"],
1313 }
tierno65ca36d2019-02-12 19:27:52 +01001314 if kwargs:
1315 # FORCE
1316 if "FORCE" in kwargs:
garciadeblas4568a372021-03-24 09:19:48 +01001317 if (
1318 kwargs["FORCE"].lower() != "false"
1319 ): # if None or True set force to True
tierno65ca36d2019-02-12 19:27:52 +01001320 admin_query["force"] = True
1321 del kwargs["FORCE"]
1322 # PUBLIC
1323 if "PUBLIC" in kwargs:
garciadeblas4568a372021-03-24 09:19:48 +01001324 if (
1325 kwargs["PUBLIC"].lower() != "false"
1326 ): # if None or True set public to True
tierno65ca36d2019-02-12 19:27:52 +01001327 admin_query["public"] = True
1328 else:
1329 admin_query["public"] = False
1330 del kwargs["PUBLIC"]
1331 # ADMIN
1332 if "ADMIN" in kwargs:
1333 behave_as = kwargs.pop("ADMIN")
1334 if behave_as.lower() != "false":
tierno701018c2019-06-25 11:13:14 +00001335 if not token_info["admin"]:
garciadeblas4568a372021-03-24 09:19:48 +01001336 raise NbiException(
1337 "Only admin projects can use 'ADMIN' query string",
1338 HTTPStatus.UNAUTHORIZED,
1339 )
1340 if (
1341 not behave_as or behave_as.lower() == "true"
1342 ): # convert True, None to empty list
tierno65ca36d2019-02-12 19:27:52 +01001343 admin_query["project_id"] = ()
1344 elif isinstance(behave_as, (list, tuple)):
1345 admin_query["project_id"] = behave_as
garciadeblas4568a372021-03-24 09:19:48 +01001346 else: # isinstance(behave_as, str)
1347 admin_query["project_id"] = (behave_as,)
tierno65ca36d2019-02-12 19:27:52 +01001348 if "SET_PROJECT" in kwargs:
1349 set_project = kwargs.pop("SET_PROJECT")
1350 if not set_project:
1351 admin_query["set_project"] = list(admin_query["project_id"])
1352 else:
1353 if isinstance(set_project, str):
garciadeblas4568a372021-03-24 09:19:48 +01001354 set_project = (set_project,)
tierno65ca36d2019-02-12 19:27:52 +01001355 if admin_query["project_id"]:
1356 for p in set_project:
1357 if p not in admin_query["project_id"]:
garciadeblas4568a372021-03-24 09:19:48 +01001358 raise NbiException(
1359 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1360 "'ADMIN='{p}'".format(p=p),
1361 HTTPStatus.UNAUTHORIZED,
1362 )
tierno65ca36d2019-02-12 19:27:52 +01001363 admin_query["set_project"] = set_project
1364
1365 # PROJECT_READ
1366 # if "PROJECT_READ" in kwargs:
1367 # admin_query["project"] = kwargs.pop("project")
tierno701018c2019-06-25 11:13:14 +00001368 # if admin_query["project"] == token_info["project_id"]:
tierno65ca36d2019-02-12 19:27:52 +01001369 if method == "GET":
1370 if _id:
1371 admin_query["method"] = "show"
1372 else:
1373 admin_query["method"] = "list"
1374 elif method == "DELETE":
1375 admin_query["method"] = "delete"
1376 else:
1377 admin_query["method"] = "write"
1378 return admin_query
1379
tiernoc94c3df2018-02-09 15:38:54 +01001380 @cherrypy.expose
garciadeblas4568a372021-03-24 09:19:48 +01001381 def default(
1382 self,
1383 main_topic=None,
1384 version=None,
1385 topic=None,
1386 _id=None,
1387 item=None,
1388 *args,
Patricia Reinoso62fa6732023-02-22 17:57:53 +00001389 **kwargs,
garciadeblas4568a372021-03-24 09:19:48 +01001390 ):
tierno701018c2019-06-25 11:13:14 +00001391 token_info = None
tiernof27c79b2018-03-12 17:08:42 +01001392 outdata = None
1393 _format = None
tierno0f98af52018-03-19 10:28:22 +01001394 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +02001395 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +02001396 rollback = []
tierno701018c2019-06-25 11:13:14 +00001397 engine_session = None
tiernoc94c3df2018-02-09 15:38:54 +01001398 try:
tiernob24258a2018-10-04 18:39:49 +02001399 if not main_topic or not version or not topic:
garciadeblas4568a372021-03-24 09:19:48 +01001400 raise NbiException(
1401 "URL must contain at least 'main_topic/version/topic'",
1402 HTTPStatus.METHOD_NOT_ALLOWED,
1403 )
1404 if main_topic not in (
1405 "admin",
1406 "vnfpkgm",
1407 "nsd",
1408 "nslcm",
1409 "pdu",
1410 "nst",
1411 "nsilcm",
1412 "nspm",
almagiae47b9132022-05-17 14:12:22 +02001413 "vnflcm",
garciadeblas4568a372021-03-24 09:19:48 +01001414 ):
1415 raise NbiException(
1416 "URL main_topic '{}' not supported".format(main_topic),
1417 HTTPStatus.METHOD_NOT_ALLOWED,
1418 )
1419 if version != "v1":
1420 raise NbiException(
1421 "URL version '{}' not supported".format(version),
1422 HTTPStatus.METHOD_NOT_ALLOWED,
1423 )
tiernoc94c3df2018-02-09 15:38:54 +01001424
garciadeblas4568a372021-03-24 09:19:48 +01001425 if (
1426 kwargs
1427 and "METHOD" in kwargs
1428 and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1429 ):
tiernof27c79b2018-03-12 17:08:42 +01001430 method = kwargs.pop("METHOD")
1431 else:
1432 method = cherrypy.request.method
tierno65ca36d2019-02-12 19:27:52 +01001433
garciadeblas4568a372021-03-24 09:19:48 +01001434 role_permission = self._check_valid_url_method(
1435 method, main_topic, version, topic, _id, item, *args
1436 )
1437 query_string_operations = self._extract_query_string_operations(
1438 kwargs, method
1439 )
tiernob24258a2018-10-04 18:39:49 +02001440 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +01001441 return self.token(method, _id, kwargs)
garciadeblas4568a372021-03-24 09:19:48 +01001442 token_info = self.authenticator.authorize(
1443 role_permission, query_string_operations, _id
1444 )
tierno12eac3c2020-03-19 23:22:08 +00001445 if main_topic == "admin" and topic == "domains":
1446 return self.domain()
tierno701018c2019-06-25 11:13:14 +00001447 engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
tiernof27c79b2018-03-12 17:08:42 +01001448 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +02001449 engine_topic = topic
preethika.p329b8182020-04-22 12:25:39 +05301450
vijay.r35ef2f72019-04-30 17:55:49 +05301451 if item and topic != "pm_jobs":
tiernob24258a2018-10-04 18:39:49 +02001452 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +01001453
tiernob24258a2018-10-04 18:39:49 +02001454 if main_topic == "nsd":
1455 engine_topic = "nsds"
1456 elif main_topic == "vnfpkgm":
1457 engine_topic = "vnfds"
delacruzramo271d2002019-12-02 21:00:37 +01001458 if topic == "vnfpkg_op_occs":
1459 engine_topic = "vnfpkgops"
1460 if topic == "vnf_packages" and item == "action":
1461 engine_topic = "vnfpkgops"
tiernob24258a2018-10-04 18:39:49 +02001462 elif main_topic == "nslcm":
1463 engine_topic = "nsrs"
1464 if topic == "ns_lcm_op_occs":
1465 engine_topic = "nslcmops"
1466 if topic == "vnfrs" or topic == "vnf_instances":
1467 engine_topic = "vnfrs"
almagiae47b9132022-05-17 14:12:22 +02001468 elif main_topic == "vnflcm":
1469 if topic == "vnf_lcm_op_occs":
1470 engine_topic = "vnflcmops"
garciadeblas9750c5a2018-10-15 16:20:35 +02001471 elif main_topic == "nst":
1472 engine_topic = "nsts"
1473 elif main_topic == "nsilcm":
1474 engine_topic = "nsis"
1475 if topic == "nsi_lcm_op_occs":
delacruzramoc061f562019-04-05 11:00:02 +02001476 engine_topic = "nsilcmops"
tiernob24258a2018-10-04 18:39:49 +02001477 elif main_topic == "pdu":
1478 engine_topic = "pdus"
garciadeblas4568a372021-03-24 09:19:48 +01001479 if (
1480 engine_topic == "vims"
1481 ): # TODO this is for backward compatibility, it will be removed in the future
tiernob24258a2018-10-04 18:39:49 +02001482 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +01001483
preethika.p329b8182020-04-22 12:25:39 +05301484 if topic == "subscriptions":
1485 engine_topic = main_topic + "_" + topic
1486
tiernoc94c3df2018-02-09 15:38:54 +01001487 if method == "GET":
garciadeblas4568a372021-03-24 09:19:48 +01001488 if item in (
1489 "nsd_content",
1490 "package_content",
1491 "artifacts",
1492 "vnfd",
1493 "nsd",
1494 "nst",
1495 "nst_content",
1496 ):
garciadeblas9750c5a2018-10-15 16:20:35 +02001497 if item in ("vnfd", "nsd", "nst"):
tiernof27c79b2018-03-12 17:08:42 +01001498 path = "$DESCRIPTOR"
1499 elif args:
1500 path = args
tiernob24258a2018-10-04 18:39:49 +02001501 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +01001502 path = ()
1503 else:
1504 path = None
garciadeblas4568a372021-03-24 09:19:48 +01001505 file, _format = self.engine.get_file(
1506 engine_session,
1507 engine_topic,
1508 _id,
1509 path,
1510 cherrypy.request.headers.get("Accept"),
1511 )
tiernof27c79b2018-03-12 17:08:42 +01001512 outdata = file
1513 elif not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001514 outdata = self.engine.get_item_list(
1515 engine_session, engine_topic, kwargs, api_req=True
1516 )
tiernoc94c3df2018-02-09 15:38:54 +01001517 else:
vijay.r35ef2f72019-04-30 17:55:49 +05301518 if item == "reports":
1519 # TODO check that project_id (_id in this context) has permissions
1520 _id = args[0]
K Sai Kiran57589552021-01-27 21:38:34 +05301521 filter_q = None
1522 if "vcaStatusRefresh" in kwargs:
1523 filter_q = {"vcaStatusRefresh": kwargs["vcaStatusRefresh"]}
garciadeblasf2af4a12023-01-24 16:56:54 +01001524 outdata = self.engine.get_item(
1525 engine_session, engine_topic, _id, filter_q, True
1526 )
delacruzramo271d2002019-12-02 21:00:37 +01001527
tiernof27c79b2018-03-12 17:08:42 +01001528 elif method == "POST":
tiernobdebce92019-07-01 15:36:49 +00001529 cherrypy.response.status = HTTPStatus.CREATED.value
garciadeblas4568a372021-03-24 09:19:48 +01001530 if topic in (
1531 "ns_descriptors_content",
1532 "vnf_packages_content",
1533 "netslice_templates_content",
1534 ):
tiernof27c79b2018-03-12 17:08:42 +01001535 _id = cherrypy.request.headers.get("Transaction-Id")
1536 if not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001537 _id, _ = self.engine.new_item(
1538 rollback,
1539 engine_session,
1540 engine_topic,
1541 {},
1542 None,
1543 cherrypy.request.headers,
1544 )
1545 completed = self.engine.upload_content(
1546 engine_session,
1547 engine_topic,
1548 _id,
1549 indata,
1550 kwargs,
1551 cherrypy.request.headers,
1552 )
tiernof27c79b2018-03-12 17:08:42 +01001553 if completed:
tiernob24258a2018-10-04 18:39:49 +02001554 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001555 else:
1556 cherrypy.response.headers["Transaction-Id"] = _id
1557 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +02001558 elif topic == "ns_instances_content":
1559 # creates NSR
garciadeblas4568a372021-03-24 09:19:48 +01001560 _id, _ = self.engine.new_item(
1561 rollback, engine_session, engine_topic, indata, kwargs
1562 )
tiernob24258a2018-10-04 18:39:49 +02001563 # creates nslcmop
1564 indata["lcmOperationType"] = "instantiate"
1565 indata["nsInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001566 nslcmop_id, _ = self.engine.new_item(
1567 rollback, engine_session, "nslcmops", indata, None
1568 )
tiernob24258a2018-10-04 18:39:49 +02001569 self._set_location_header(main_topic, version, topic, _id)
kuuse078f55e2019-05-16 19:24:21 +02001570 outdata = {"id": _id, "nslcmop_id": nslcmop_id}
tiernob24258a2018-10-04 18:39:49 +02001571 elif topic == "ns_instances" and item:
1572 indata["lcmOperationType"] = item
1573 indata["nsInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001574 _id, _ = self.engine.new_item(
1575 rollback, engine_session, "nslcmops", indata, kwargs
1576 )
1577 self._set_location_header(
1578 main_topic, version, "ns_lcm_op_occs", _id
1579 )
tierno65acb4d2018-04-06 16:42:40 +02001580 outdata = {"id": _id}
1581 cherrypy.response.status = HTTPStatus.ACCEPTED.value
garciadeblas9750c5a2018-10-15 16:20:35 +02001582 elif topic == "netslice_instances_content":
Felipe Vicens07f31722018-10-29 15:16:44 +01001583 # creates NetSlice_Instance_record (NSIR)
garciadeblas4568a372021-03-24 09:19:48 +01001584 _id, _ = self.engine.new_item(
1585 rollback, engine_session, engine_topic, indata, kwargs
1586 )
Felipe Vicens07f31722018-10-29 15:16:44 +01001587 self._set_location_header(main_topic, version, topic, _id)
garciadeblas9750c5a2018-10-15 16:20:35 +02001588 indata["lcmOperationType"] = "instantiate"
Felipe Vicens126af572019-06-05 19:13:04 +02001589 indata["netsliceInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001590 nsilcmop_id, _ = self.engine.new_item(
1591 rollback, engine_session, "nsilcmops", indata, kwargs
1592 )
kuuse078f55e2019-05-16 19:24:21 +02001593 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
garciadeblas9750c5a2018-10-15 16:20:35 +02001594 elif topic == "netslice_instances" and item:
1595 indata["lcmOperationType"] = item
Felipe Vicens126af572019-06-05 19:13:04 +02001596 indata["netsliceInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001597 _id, _ = self.engine.new_item(
1598 rollback, engine_session, "nsilcmops", indata, kwargs
1599 )
1600 self._set_location_header(
1601 main_topic, version, "nsi_lcm_op_occs", _id
1602 )
garciadeblas9750c5a2018-10-15 16:20:35 +02001603 outdata = {"id": _id}
1604 cherrypy.response.status = HTTPStatus.ACCEPTED.value
delacruzramo271d2002019-12-02 21:00:37 +01001605 elif topic == "vnf_packages" and item == "action":
1606 indata["lcmOperationType"] = item
1607 indata["vnfPkgId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001608 _id, _ = self.engine.new_item(
1609 rollback, engine_session, "vnfpkgops", indata, kwargs
1610 )
1611 self._set_location_header(
1612 main_topic, version, "vnfpkg_op_occs", _id
1613 )
delacruzramo271d2002019-12-02 21:00:37 +01001614 outdata = {"id": _id}
1615 cherrypy.response.status = HTTPStatus.ACCEPTED.value
preethika.p329b8182020-04-22 12:25:39 +05301616 elif topic == "subscriptions":
garciadeblas4568a372021-03-24 09:19:48 +01001617 _id, _ = self.engine.new_item(
1618 rollback, engine_session, engine_topic, indata, kwargs
1619 )
preethika.p329b8182020-04-22 12:25:39 +05301620 self._set_location_header(main_topic, version, topic, _id)
1621 link = {}
1622 link["self"] = cherrypy.response.headers["Location"]
garciadeblas4568a372021-03-24 09:19:48 +01001623 outdata = {
1624 "id": _id,
1625 "filter": indata["filter"],
1626 "callbackUri": indata["CallbackUri"],
1627 "_links": link,
1628 }
preethika.p329b8182020-04-22 12:25:39 +05301629 cherrypy.response.status = HTTPStatus.CREATED.value
almagiae47b9132022-05-17 14:12:22 +02001630 elif topic == "vnf_instances" and item:
1631 indata["lcmOperationType"] = item
1632 indata["vnfInstanceId"] = _id
garciadeblasf2af4a12023-01-24 16:56:54 +01001633 _id, _ = self.engine.new_item(
1634 rollback, engine_session, "vnflcmops", indata, kwargs
1635 )
1636 self._set_location_header(
1637 main_topic, version, "vnf_lcm_op_occs", _id
1638 )
almagiae47b9132022-05-17 14:12:22 +02001639 outdata = {"id": _id}
1640 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +01001641 else:
garciadeblas4568a372021-03-24 09:19:48 +01001642 _id, op_id = self.engine.new_item(
1643 rollback,
1644 engine_session,
1645 engine_topic,
1646 indata,
1647 kwargs,
1648 cherrypy.request.headers,
1649 )
tiernob24258a2018-10-04 18:39:49 +02001650 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001651 outdata = {"id": _id}
tiernobdebce92019-07-01 15:36:49 +00001652 if op_id:
1653 outdata["op_id"] = op_id
1654 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernob24258a2018-10-04 18:39:49 +02001655 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tierno09c073e2018-04-26 13:36:48 +02001656
tiernoc94c3df2018-02-09 15:38:54 +01001657 elif method == "DELETE":
1658 if not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001659 outdata = self.engine.del_item_list(
1660 engine_session, engine_topic, kwargs
1661 )
tierno09c073e2018-04-26 13:36:48 +02001662 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +01001663 else: # len(args) > 1
tierno22577432020-04-08 15:16:57 +00001664 # for NS NSI generate an operation
1665 op_id = None
tierno701018c2019-06-25 11:13:14 +00001666 if topic == "ns_instances_content" and not engine_session["force"]:
tiernob24258a2018-10-04 18:39:49 +02001667 nslcmop_desc = {
1668 "lcmOperationType": "terminate",
1669 "nsInstanceId": _id,
garciadeblas4568a372021-03-24 09:19:48 +01001670 "autoremove": True,
tiernob24258a2018-10-04 18:39:49 +02001671 }
garciadeblas4568a372021-03-24 09:19:48 +01001672 op_id, _ = self.engine.new_item(
1673 rollback, engine_session, "nslcmops", nslcmop_desc, kwargs
1674 )
tierno22577432020-04-08 15:16:57 +00001675 if op_id:
1676 outdata = {"_id": op_id}
garciadeblas4568a372021-03-24 09:19:48 +01001677 elif (
1678 topic == "netslice_instances_content"
1679 and not engine_session["force"]
1680 ):
garciadeblas9750c5a2018-10-15 16:20:35 +02001681 nsilcmop_desc = {
1682 "lcmOperationType": "terminate",
Felipe Vicens126af572019-06-05 19:13:04 +02001683 "netsliceInstanceId": _id,
garciadeblas4568a372021-03-24 09:19:48 +01001684 "autoremove": True,
garciadeblas9750c5a2018-10-15 16:20:35 +02001685 }
garciadeblas4568a372021-03-24 09:19:48 +01001686 op_id, _ = self.engine.new_item(
1687 rollback, engine_session, "nsilcmops", nsilcmop_desc, None
1688 )
tierno22577432020-04-08 15:16:57 +00001689 if op_id:
1690 outdata = {"_id": op_id}
1691 # if there is not any deletion in process, delete
1692 if not op_id:
1693 op_id = self.engine.del_item(engine_session, engine_topic, _id)
1694 if op_id:
1695 outdata = {"op_id": op_id}
garciadeblas4568a372021-03-24 09:19:48 +01001696 cherrypy.response.status = (
1697 HTTPStatus.ACCEPTED.value
1698 if op_id
1699 else HTTPStatus.NO_CONTENT.value
1700 )
tierno09c073e2018-04-26 13:36:48 +02001701
tierno7ae10112018-05-18 14:36:02 +02001702 elif method in ("PUT", "PATCH"):
tiernobdebce92019-07-01 15:36:49 +00001703 op_id = None
tierno701018c2019-06-25 11:13:14 +00001704 if not indata and not kwargs and not engine_session.get("set_project"):
garciadeblas4568a372021-03-24 09:19:48 +01001705 raise NbiException(
1706 "Nothing to update. Provide payload and/or query string",
1707 HTTPStatus.BAD_REQUEST,
1708 )
1709 if (
1710 item in ("nsd_content", "package_content", "nst_content")
1711 and method == "PUT"
1712 ):
1713 completed = self.engine.upload_content(
1714 engine_session,
1715 engine_topic,
1716 _id,
1717 indata,
1718 kwargs,
1719 cherrypy.request.headers,
1720 )
tiernof27c79b2018-03-12 17:08:42 +01001721 if not completed:
1722 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +01001723 else:
garciadeblas4568a372021-03-24 09:19:48 +01001724 op_id = self.engine.edit_item(
1725 engine_session, engine_topic, _id, indata, kwargs
1726 )
tiernobdebce92019-07-01 15:36:49 +00001727
1728 if op_id:
1729 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1730 outdata = {"op_id": op_id}
1731 else:
1732 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
1733 outdata = None
tiernoc94c3df2018-02-09 15:38:54 +01001734 else:
garciadeblas4568a372021-03-24 09:19:48 +01001735 raise NbiException(
1736 "Method {} not allowed".format(method),
1737 HTTPStatus.METHOD_NOT_ALLOWED,
1738 )
tiernoa6bb45d2019-06-14 09:45:39 +00001739
1740 # if Role information changes, it is needed to reload the information of roles
1741 if topic == "roles" and method != "GET":
1742 self.authenticator.load_operation_to_allowed_roles()
delacruzramoad682a52019-12-10 16:26:34 +01001743
garciadeblas4568a372021-03-24 09:19:48 +01001744 if (
1745 topic == "projects"
1746 and method == "DELETE"
1747 or topic in ["users", "roles"]
1748 and method in ["PUT", "PATCH", "DELETE"]
1749 ):
delacruzramoad682a52019-12-10 16:26:34 +01001750 self.authenticator.remove_token_from_cache()
1751
tierno701018c2019-06-25 11:13:14 +00001752 return self._format_out(outdata, token_info, _format)
tiernob24258a2018-10-04 18:39:49 +02001753 except Exception as e:
garciadeblas4568a372021-03-24 09:19:48 +01001754 if isinstance(
1755 e,
1756 (
1757 NbiException,
1758 EngineException,
1759 DbException,
1760 FsException,
1761 MsgException,
1762 AuthException,
1763 ValidationError,
1764 AuthconnException,
1765 ),
1766 ):
tiernob24258a2018-10-04 18:39:49 +02001767 http_code_value = cherrypy.response.status = e.http_code.value
1768 http_code_name = e.http_code.name
1769 cherrypy.log("Exception {}".format(e))
1770 else:
garciadeblas4568a372021-03-24 09:19:48 +01001771 http_code_value = (
1772 cherrypy.response.status
1773 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Felipe Vicense36ab852018-11-23 14:12:09 +01001774 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
tiernob24258a2018-10-04 18:39:49 +02001775 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +02001776 if hasattr(outdata, "close"): # is an open file
1777 outdata.close()
tiernob24258a2018-10-04 18:39:49 +02001778 error_text = str(e)
1779 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +02001780 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +02001781 try:
tiernocc103432018-10-19 14:10:35 +02001782 if rollback_item.get("operation") == "set":
garciadeblas4568a372021-03-24 09:19:48 +01001783 self.engine.db.set_one(
1784 rollback_item["topic"],
1785 {"_id": rollback_item["_id"]},
1786 rollback_item["content"],
1787 fail_on_empty=False,
1788 )
preethika.p329b8182020-04-22 12:25:39 +05301789 elif rollback_item.get("operation") == "del_list":
garciadeblas4568a372021-03-24 09:19:48 +01001790 self.engine.db.del_list(
1791 rollback_item["topic"],
1792 rollback_item["filter"],
garciadeblas4568a372021-03-24 09:19:48 +01001793 )
tiernocc103432018-10-19 14:10:35 +02001794 else:
garciadeblas4568a372021-03-24 09:19:48 +01001795 self.engine.db.del_one(
1796 rollback_item["topic"],
1797 {"_id": rollback_item["_id"]},
1798 fail_on_empty=False,
1799 )
tierno3ace63c2018-05-03 17:51:43 +02001800 except Exception as e2:
garciadeblas4568a372021-03-24 09:19:48 +01001801 rollback_error_text = "Rollback Exception {}: {}".format(
1802 rollback_item, e2
1803 )
tiernob24258a2018-10-04 18:39:49 +02001804 cherrypy.log(rollback_error_text)
1805 error_text += ". " + rollback_error_text
1806 # if isinstance(e, MsgException):
1807 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1808 # engine_topic[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +01001809 problem_details = {
tiernob24258a2018-10-04 18:39:49 +02001810 "code": http_code_name,
1811 "status": http_code_value,
1812 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +01001813 }
tierno701018c2019-06-25 11:13:14 +00001814 return self._format_out(problem_details, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001815 # raise cherrypy.HTTPError(e.http_code.value, str(e))
tiernoa5035702019-07-29 08:54:42 +00001816 finally:
1817 if token_info:
1818 self._format_login(token_info)
1819 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
1820 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1821 if outdata.get(logging_id):
garciadeblas4568a372021-03-24 09:19:48 +01001822 cherrypy.request.login += ";{}={}".format(
1823 logging_id, outdata[logging_id][:36]
1824 )
tiernoc94c3df2018-02-09 15:38:54 +01001825
1826
tiernoc94c3df2018-02-09 15:38:54 +01001827def _start_service():
1828 """
1829 Callback function called when cherrypy.engine starts
1830 Override configuration with env variables
1831 Set database, storage, message configuration
1832 Init database with admin/admin user password
1833 """
tierno932499c2019-01-28 17:28:10 +00001834 global nbi_server
1835 global subscription_thread
tiernoc94c3df2018-02-09 15:38:54 +01001836 cherrypy.log.error("Starting osm_nbi")
1837 # update general cherrypy configuration
1838 update_dict = {}
1839
garciadeblas4568a372021-03-24 09:19:48 +01001840 engine_config = cherrypy.tree.apps["/osm"].config
tiernoc94c3df2018-02-09 15:38:54 +01001841 for k, v in environ.items():
1842 if not k.startswith("OSMNBI_"):
1843 continue
tiernoe1281182018-05-22 12:24:36 +02001844 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +01001845 if not k2:
1846 continue
1847 try:
1848 # update static configuration
garciadeblas4568a372021-03-24 09:19:48 +01001849 if k == "OSMNBI_STATIC_DIR":
1850 engine_config["/static"]["tools.staticdir.dir"] = v
1851 engine_config["/static"]["tools.staticdir.on"] = True
1852 elif k == "OSMNBI_SOCKET_PORT" or k == "OSMNBI_SERVER_PORT":
1853 update_dict["server.socket_port"] = int(v)
1854 elif k == "OSMNBI_SOCKET_HOST" or k == "OSMNBI_SERVER_HOST":
1855 update_dict["server.socket_host"] = v
tiernof5298be2018-05-16 14:43:57 +02001856 elif k1 in ("server", "test", "auth", "log"):
garciadeblas4568a372021-03-24 09:19:48 +01001857 update_dict[k1 + "." + k2] = v
Patricia Reinoso62fa6732023-02-22 17:57:53 +00001858 elif k1 in ("message", "database", "storage", "authentication", "temporal"):
tiernof5298be2018-05-16 14:43:57 +02001859 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001860 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +01001861 engine_config[k1][k2] = int(v)
1862 else:
1863 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001864
tiernoc94c3df2018-02-09 15:38:54 +01001865 except ValueError as e:
1866 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1867 except Exception as e:
garciadeblasf2af4a12023-01-24 16:56:54 +01001868 cherrypy.log(
1869 "WARNING: skipping environ '{}' on exception '{}'".format(k, e)
1870 )
tiernoc94c3df2018-02-09 15:38:54 +01001871
1872 if update_dict:
1873 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +02001874 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001875
1876 # logging cherrypy
garciadeblas4568a372021-03-24 09:19:48 +01001877 log_format_simple = (
1878 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1879 )
1880 log_formatter_simple = logging.Formatter(
1881 log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S"
1882 )
tiernoc94c3df2018-02-09 15:38:54 +01001883 logger_server = logging.getLogger("cherrypy.error")
1884 logger_access = logging.getLogger("cherrypy.access")
1885 logger_cherry = logging.getLogger("cherrypy")
1886 logger_nbi = logging.getLogger("nbi")
1887
tiernof5298be2018-05-16 14:43:57 +02001888 if "log.file" in engine_config["global"]:
garciadeblas4568a372021-03-24 09:19:48 +01001889 file_handler = logging.handlers.RotatingFileHandler(
1890 engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0
1891 )
tiernoc94c3df2018-02-09 15:38:54 +01001892 file_handler.setFormatter(log_formatter_simple)
1893 logger_cherry.addHandler(file_handler)
1894 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +02001895 # log always to standard output
garciadeblas4568a372021-03-24 09:19:48 +01001896 for format_, logger in {
1897 "nbi.server %(filename)s:%(lineno)s": logger_server,
1898 "nbi.access %(filename)s:%(lineno)s": logger_access,
1899 "%(name)s %(filename)s:%(lineno)s": logger_nbi,
1900 }.items():
tiernob24258a2018-10-04 18:39:49 +02001901 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
garciadeblas4568a372021-03-24 09:19:48 +01001902 log_formatter_cherry = logging.Formatter(
1903 log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S"
1904 )
tiernob24258a2018-10-04 18:39:49 +02001905 str_handler = logging.StreamHandler()
1906 str_handler.setFormatter(log_formatter_cherry)
1907 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +01001908
tiernof5298be2018-05-16 14:43:57 +02001909 if engine_config["global"].get("log.level"):
1910 logger_cherry.setLevel(engine_config["global"]["log.level"])
1911 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +01001912
1913 # logging other modules
garciadeblas4568a372021-03-24 09:19:48 +01001914 for k1, logname in {
1915 "message": "nbi.msg",
1916 "database": "nbi.db",
1917 "storage": "nbi.fs",
1918 }.items():
tiernoc94c3df2018-02-09 15:38:54 +01001919 engine_config[k1]["logger_name"] = logname
1920 logger_module = logging.getLogger(logname)
1921 if "logfile" in engine_config[k1]:
garciadeblas4568a372021-03-24 09:19:48 +01001922 file_handler = logging.handlers.RotatingFileHandler(
1923 engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0
1924 )
tiernoc94c3df2018-02-09 15:38:54 +01001925 file_handler.setFormatter(log_formatter_simple)
1926 logger_module.addHandler(file_handler)
1927 if "loglevel" in engine_config[k1]:
1928 logger_module.setLevel(engine_config[k1]["loglevel"])
1929 # TODO add more entries, e.g.: storage
garciadeblas4568a372021-03-24 09:19:48 +01001930 cherrypy.tree.apps["/osm"].root.engine.start(engine_config)
1931 cherrypy.tree.apps["/osm"].root.authenticator.start(engine_config)
1932 cherrypy.tree.apps["/osm"].root.engine.init_db(target_version=database_version)
1933 cherrypy.tree.apps["/osm"].root.authenticator.init_db(
1934 target_version=auth_database_version
1935 )
tiernobee508e2019-01-21 11:21:49 +00001936
tierno932499c2019-01-28 17:28:10 +00001937 # start subscriptions thread:
garciadeblas4568a372021-03-24 09:19:48 +01001938 subscription_thread = SubscriptionThread(
1939 config=engine_config, engine=nbi_server.engine
1940 )
tierno932499c2019-01-28 17:28:10 +00001941 subscription_thread.start()
1942 # Do not capture except SubscriptionException
1943
Patricia Reinoso62fa6732023-02-22 17:57:53 +00001944 WFTemporal.temporal_api = (
1945 f'{engine_config["temporal"]["host"]}:{engine_config["temporal"]["port"]}'
1946 )
1947
tiernob2e48bd2020-02-04 15:47:18 +00001948 backend = engine_config["authentication"]["backend"]
garciadeblas4568a372021-03-24 09:19:48 +01001949 cherrypy.log.error(
1950 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1951 nbi_version, nbi_version_date, backend
1952 )
1953 )
tiernoc94c3df2018-02-09 15:38:54 +01001954
1955
1956def _stop_service():
1957 """
1958 Callback function called when cherrypy.engine stops
1959 TODO: Ending database connections.
1960 """
tierno932499c2019-01-28 17:28:10 +00001961 global subscription_thread
tierno65ca36d2019-02-12 19:27:52 +01001962 if subscription_thread:
1963 subscription_thread.terminate()
tierno932499c2019-01-28 17:28:10 +00001964 subscription_thread = None
garciadeblas4568a372021-03-24 09:19:48 +01001965 cherrypy.tree.apps["/osm"].root.engine.stop()
tiernoc94c3df2018-02-09 15:38:54 +01001966 cherrypy.log.error("Stopping osm_nbi")
1967
tierno2236d202018-05-16 19:05:16 +02001968
tiernof5298be2018-05-16 14:43:57 +02001969def nbi(config_file):
tierno932499c2019-01-28 17:28:10 +00001970 global nbi_server
tiernoc94c3df2018-02-09 15:38:54 +01001971 # conf = {
1972 # '/': {
1973 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1974 # 'tools.sessions.on': True,
1975 # 'tools.response_headers.on': True,
1976 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1977 # }
1978 # }
1979 # cherrypy.Server.ssl_module = 'builtin'
1980 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1981 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1982 # cherrypy.Server.thread_pool = 10
1983 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1984
1985 # cherrypy.config.update({'tools.auth_basic.on': True,
1986 # 'tools.auth_basic.realm': 'localhost',
1987 # 'tools.auth_basic.checkpassword': validate_password})
tierno932499c2019-01-28 17:28:10 +00001988 nbi_server = Server()
garciadeblas4568a372021-03-24 09:19:48 +01001989 cherrypy.engine.subscribe("start", _start_service)
1990 cherrypy.engine.subscribe("stop", _stop_service)
1991 cherrypy.quickstart(nbi_server, "/osm", config_file)
tiernof5298be2018-05-16 14:43:57 +02001992
1993
1994def usage():
garciadeblas4568a372021-03-24 09:19:48 +01001995 print(
1996 """Usage: {} [options]
tiernof5298be2018-05-16 14:43:57 +02001997 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1998 -h|--help: shows this help
garciadeblas4568a372021-03-24 09:19:48 +01001999 """.format(
2000 sys.argv[0]
2001 )
2002 )
tierno2236d202018-05-16 19:05:16 +02002003 # --log-socket-host HOST: send logs to this host")
2004 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +01002005
2006
garciadeblas4568a372021-03-24 09:19:48 +01002007if __name__ == "__main__":
tiernof5298be2018-05-16 14:43:57 +02002008 try:
2009 # load parameters and configuration
2010 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
2011 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
2012 config_file = None
2013 for o, a in opts:
2014 if o in ("-h", "--help"):
2015 usage()
2016 sys.exit()
2017 elif o in ("-c", "--config"):
2018 config_file = a
2019 # elif o == "--log-socket-port":
2020 # log_socket_port = a
2021 # elif o == "--log-socket-host":
2022 # log_socket_host = a
2023 # elif o == "--log-file":
2024 # log_file = a
2025 else:
2026 assert False, "Unhandled option"
2027 if config_file:
2028 if not path.isfile(config_file):
garciadeblas4568a372021-03-24 09:19:48 +01002029 print(
2030 "configuration file '{}' that not exist".format(config_file),
2031 file=sys.stderr,
2032 )
tiernof5298be2018-05-16 14:43:57 +02002033 exit(1)
2034 else:
garciadeblas4568a372021-03-24 09:19:48 +01002035 for config_file in (
2036 __file__[: __file__.rfind(".")] + ".cfg",
2037 "./nbi.cfg",
2038 "/etc/osm/nbi.cfg",
2039 ):
tiernof5298be2018-05-16 14:43:57 +02002040 if path.isfile(config_file):
2041 break
2042 else:
garciadeblas4568a372021-03-24 09:19:48 +01002043 print(
2044 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
2045 file=sys.stderr,
2046 )
tiernof5298be2018-05-16 14:43:57 +02002047 exit(1)
2048 nbi(config_file)
2049 except getopt.GetoptError as e:
2050 print(str(e), file=sys.stderr)
2051 # usage()
2052 exit(1)