blob: 8fa61522bac0388a227189339e7192e674c53560 [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
tiernoc94c3df2018-02-09 15:38:54 +010035from http import HTTPStatus
tiernoc94c3df2018-02-09 15:38:54 +010036from codecs import getreader
tiernof5298be2018-05-16 14:43:57 +020037from os import environ, path
tiernob2e48bd2020-02-04 15:47:18 +000038from osm_nbi import version as nbi_version, version_date as nbi_version_date
tiernoc94c3df2018-02-09 15:38:54 +010039
40__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
tiernodfe09572018-04-24 10:41:10 +020041
garciadeblas4568a372021-03-24 09:19:48 +010042__version__ = "0.1.3" # file version, not NBI version
tierno9c630112019-08-29 14:21:41 +000043version_date = "Aug 2019"
44
garciadeblas4568a372021-03-24 09:19:48 +010045database_version = "1.2"
46auth_database_version = "1.0"
47nbi_server = None # instance of Server class
tierno932499c2019-01-28 17:28:10 +000048subscription_thread = None # instance of SubscriptionThread class
tiernoc94c3df2018-02-09 15:38:54 +010049
50"""
tiernof27c79b2018-03-12 17:08:42 +010051North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
tiernoc94c3df2018-02-09 15:38:54 +010052URL: /osm GET POST PUT DELETE PATCH
garciadeblas9750c5a2018-10-15 16:20:35 +020053 /nsd/v1
tierno2236d202018-05-16 19:05:16 +020054 /ns_descriptors_content O O
55 /<nsdInfoId> O O O O
tiernoc94c3df2018-02-09 15:38:54 +010056 /ns_descriptors O5 O5
57 /<nsdInfoId> O5 O5 5
58 /nsd_content O5 O5
tiernof27c79b2018-03-12 17:08:42 +010059 /nsd O
60 /artifacts[/<artifactPath>] O
tiernoc94c3df2018-02-09 15:38:54 +010061 /pnf_descriptors 5 5
62 /<pnfdInfoId> 5 5 5
63 /pnfd_content 5 5
tiernof27c79b2018-03-12 17:08:42 +010064 /subscriptions 5 5
65 /<subscriptionId> 5 X
tiernoc94c3df2018-02-09 15:38:54 +010066
67 /vnfpkgm/v1
tierno55945e72018-04-06 16:40:27 +020068 /vnf_packages_content O O
tierno2236d202018-05-16 19:05:16 +020069 /<vnfPkgId> O O
tiernoc94c3df2018-02-09 15:38:54 +010070 /vnf_packages O5 O5
71 /<vnfPkgId> O5 O5 5
tiernoc94c3df2018-02-09 15:38:54 +010072 /package_content O5 O5
73 /upload_from_uri X
tiernof27c79b2018-03-12 17:08:42 +010074 /vnfd O5
75 /artifacts[/<artifactPath>] O5
76 /subscriptions X X
77 /<subscriptionId> X X
tiernoc94c3df2018-02-09 15:38:54 +010078
79 /nslcm/v1
tiernof27c79b2018-03-12 17:08:42 +010080 /ns_instances_content O O
tierno2236d202018-05-16 19:05:16 +020081 /<nsInstanceId> O O
tiernof27c79b2018-03-12 17:08:42 +010082 /ns_instances 5 5
tierno95692442018-05-24 18:05:28 +020083 /<nsInstanceId> O5 O5
tierno65acb4d2018-04-06 16:42:40 +020084 instantiate O5
85 terminate O5
86 action O
87 scale O5
elumalai8e3806c2022-04-28 17:26:24 +053088 migrate O
aticig544a2ae2022-04-05 09:00:17 +030089 update 05
garciadeblas0964edf2022-02-11 00:43:44 +010090 heal O5
tiernoc94c3df2018-02-09 15:38:54 +010091 /ns_lcm_op_occs 5 5
92 /<nsLcmOpOccId> 5 5 5
93 TO BE COMPLETED 5 5
tiernof759d822018-06-11 18:54:54 +020094 /vnf_instances (also vnfrs for compatibility) O
95 /<vnfInstanceId> O
tiernof27c79b2018-03-12 17:08:42 +010096 /subscriptions 5 5
97 /<subscriptionId> 5 X
garciadeblas9750c5a2018-10-15 16:20:35 +020098
tiernocb83c942018-09-24 17:28:13 +020099 /pdu/v1
tierno032916c2019-03-22 13:27:12 +0000100 /pdu_descriptors O O
tiernocb83c942018-09-24 17:28:13 +0200101 /<id> O O O O
garciadeblas9750c5a2018-10-15 16:20:35 +0200102
tiernof27c79b2018-03-12 17:08:42 +0100103 /admin/v1
104 /tokens O O
tierno2236d202018-05-16 19:05:16 +0200105 /<id> O O
tiernof27c79b2018-03-12 17:08:42 +0100106 /users O O
tiernocd54a4a2018-09-12 16:40:35 +0200107 /<id> O O O O
tiernof27c79b2018-03-12 17:08:42 +0100108 /projects O O
tierno2236d202018-05-16 19:05:16 +0200109 /<id> O O
tierno55ba2e62018-12-11 17:22:22 +0000110 /vim_accounts (also vims for compatibility) O O
111 /<id> O O O
112 /wim_accounts O O
tierno2236d202018-05-16 19:05:16 +0200113 /<id> O O O
tierno0f98af52018-03-19 10:28:22 +0100114 /sdns O O
tierno2236d202018-05-16 19:05:16 +0200115 /<id> O O O
delacruzramofe598fe2019-10-23 18:25:11 +0200116 /k8sclusters O O
117 /<id> O O O
118 /k8srepos O O
119 /<id> O O
Felipe Vicensb66b0412020-05-06 10:11:00 +0200120 /osmrepos O O
121 /<id> O O
tiernoc94c3df2018-02-09 15:38:54 +0100122
garciadeblas9750c5a2018-10-15 16:20:35 +0200123 /nst/v1 O O
124 /netslice_templates_content O O
125 /<nstInfoId> O O O O
126 /netslice_templates O O
127 /<nstInfoId> O O O
128 /nst_content O O
129 /nst O
130 /artifacts[/<artifactPath>] O
131 /subscriptions X X
132 /<subscriptionId> X X
133
134 /nsilcm/v1
135 /netslice_instances_content O O
136 /<SliceInstanceId> O O
137 /netslice_instances O O
138 /<SliceInstanceId> O O
139 instantiate O
140 terminate O
141 action O
142 /nsi_lcm_op_occs O O
143 /<nsiLcmOpOccId> O O O
144 /subscriptions X X
145 /<subscriptionId> X X
146
tierno2236d202018-05-16 19:05:16 +0200147query string:
148 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
tierno36ec8602018-11-02 17:27:11 +0100149 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
150 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
151 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
152 attrName := string
tierno2236d202018-05-16 19:05:16 +0200153 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
154 item of the array, that is, pass if any item of the array pass the filter.
155 It allows both ne and neq for not equal
156 TODO: 4.3.3 Attribute selectors
157 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
tiernoc94c3df2018-02-09 15:38:54 +0100158 (none) … same as “exclude_default”
159 all_fields … all attributes.
tierno2236d202018-05-16 19:05:16 +0200160 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
161 conditionally mandatory, and that are not provided in <list>.
162 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
163 are not conditionally mandatory, and that are provided in <list>.
164 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
165 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
166 the particular resource
167 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
168 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
169 present specification for the particular resource, but that are not part of <list>
tierno65ca36d2019-02-12 19:27:52 +0100170 Additionally it admits some administrator values:
171 FORCE: To force operations skipping dependency checkings
172 ADMIN: To act as an administrator or a different project
173 PUBLIC: To get public descriptors or set a descriptor as public
174 SET_PROJECT: To make a descriptor available for other project
beierlmbc5a5242022-05-17 21:25:29 -0400175
tiernoc94c3df2018-02-09 15:38:54 +0100176Header field name Reference Example Descriptions
177 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
178 This header field shall be present if the response is expected to have a non-empty message body.
179 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
180 This header field shall be present if the request has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200181 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
182 Details are specified in clause 4.5.3.
tiernoc94c3df2018-02-09 15:38:54 +0100183 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
184Header field name Reference Example Descriptions
185 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
186 This header field shall be present if the response has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200187 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
188 new resource has been created.
tiernoc94c3df2018-02-09 15:38:54 +0100189 This header field shall be present if the response status code is 201 or 3xx.
tierno2236d202018-05-16 19:05:16 +0200190 In the present document this header field is also used if the response status code is 202 and a new resource was
191 created.
192 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
193 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
194 token.
195 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
196 certain resources.
197 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
198 response, and the total length of the file.
tiernoc94c3df2018-02-09 15:38:54 +0100199 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
tiernoc94c3df2018-02-09 15:38:54 +0100200"""
201
tierno701018c2019-06-25 11:13:14 +0000202valid_query_string = ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
203# ^ Contains possible administrative query string words:
204# ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
205# (not owned by my session project).
206# PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
207# FORCE=True(by default)|False: Force edition/deletion operations
208# SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
209
210valid_url_methods = {
211 # contains allowed URL and methods, and the role_permission name
212 "admin": {
213 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100214 "tokens": {
215 "METHODS": ("GET", "POST", "DELETE"),
216 "ROLE_PERMISSION": "tokens:",
217 "<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"},
218 },
219 "users": {
220 "METHODS": ("GET", "POST"),
221 "ROLE_PERMISSION": "users:",
222 "<ID>": {
223 "METHODS": ("GET", "DELETE", "PATCH"),
224 "ROLE_PERMISSION": "users:id:",
225 },
226 },
227 "projects": {
228 "METHODS": ("GET", "POST"),
229 "ROLE_PERMISSION": "projects:",
230 "<ID>": {
231 "METHODS": ("GET", "DELETE", "PATCH"),
232 "ROLE_PERMISSION": "projects:id:",
233 },
234 },
235 "roles": {
236 "METHODS": ("GET", "POST"),
237 "ROLE_PERMISSION": "roles:",
238 "<ID>": {
239 "METHODS": ("GET", "DELETE", "PATCH"),
240 "ROLE_PERMISSION": "roles:id:",
241 },
242 },
243 "vims": {
244 "METHODS": ("GET", "POST"),
245 "ROLE_PERMISSION": "vims:",
246 "<ID>": {
247 "METHODS": ("GET", "DELETE", "PATCH"),
248 "ROLE_PERMISSION": "vims:id:",
249 },
250 },
251 "vim_accounts": {
252 "METHODS": ("GET", "POST"),
253 "ROLE_PERMISSION": "vim_accounts:",
254 "<ID>": {
255 "METHODS": ("GET", "DELETE", "PATCH"),
256 "ROLE_PERMISSION": "vim_accounts:id:",
257 },
258 },
259 "wim_accounts": {
260 "METHODS": ("GET", "POST"),
261 "ROLE_PERMISSION": "wim_accounts:",
262 "<ID>": {
263 "METHODS": ("GET", "DELETE", "PATCH"),
264 "ROLE_PERMISSION": "wim_accounts:id:",
265 },
266 },
267 "sdns": {
268 "METHODS": ("GET", "POST"),
269 "ROLE_PERMISSION": "sdn_controllers:",
270 "<ID>": {
271 "METHODS": ("GET", "DELETE", "PATCH"),
272 "ROLE_PERMISSION": "sdn_controllers:id:",
273 },
274 },
275 "k8sclusters": {
276 "METHODS": ("GET", "POST"),
277 "ROLE_PERMISSION": "k8sclusters:",
278 "<ID>": {
279 "METHODS": ("GET", "DELETE", "PATCH"),
280 "ROLE_PERMISSION": "k8sclusters:id:",
281 },
282 },
283 "vca": {
284 "METHODS": ("GET", "POST"),
285 "ROLE_PERMISSION": "vca:",
286 "<ID>": {
287 "METHODS": ("GET", "DELETE", "PATCH"),
288 "ROLE_PERMISSION": "vca:id:",
289 },
290 },
291 "k8srepos": {
292 "METHODS": ("GET", "POST"),
293 "ROLE_PERMISSION": "k8srepos:",
294 "<ID>": {
295 "METHODS": ("GET", "DELETE"),
296 "ROLE_PERMISSION": "k8srepos:id:",
297 },
298 },
299 "osmrepos": {
300 "METHODS": ("GET", "POST"),
301 "ROLE_PERMISSION": "osmrepos:",
302 "<ID>": {
303 "METHODS": ("GET", "DELETE", "PATCH"),
304 "ROLE_PERMISSION": "osmrepos:id:",
305 },
306 },
307 "domains": {
308 "METHODS": ("GET",),
309 "ROLE_PERMISSION": "domains:",
310 },
tierno701018c2019-06-25 11:13:14 +0000311 }
312 },
313 "pdu": {
314 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100315 "pdu_descriptors": {
316 "METHODS": ("GET", "POST"),
317 "ROLE_PERMISSION": "pduds:",
318 "<ID>": {
319 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
320 "ROLE_PERMISSION": "pduds:id:",
321 },
322 },
tierno701018c2019-06-25 11:13:14 +0000323 }
324 },
325 "nsd": {
326 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100327 "ns_descriptors_content": {
328 "METHODS": ("GET", "POST"),
329 "ROLE_PERMISSION": "nsds:",
330 "<ID>": {
331 "METHODS": ("GET", "PUT", "DELETE"),
332 "ROLE_PERMISSION": "nsds:id:",
333 },
334 },
335 "ns_descriptors": {
336 "METHODS": ("GET", "POST"),
337 "ROLE_PERMISSION": "nsds:",
338 "<ID>": {
339 "METHODS": ("GET", "DELETE", "PATCH"),
340 "ROLE_PERMISSION": "nsds:id:",
341 "nsd_content": {
342 "METHODS": ("GET", "PUT"),
343 "ROLE_PERMISSION": "nsds:id:content:",
344 },
345 "nsd": {
346 "METHODS": ("GET",), # descriptor inside package
347 "ROLE_PERMISSION": "nsds:id:content:",
348 },
349 "artifacts": {
350 "METHODS": ("GET",),
351 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
352 "*": None,
353 },
354 },
355 },
356 "pnf_descriptors": {
357 "TODO": ("GET", "POST"),
358 "<ID>": {
359 "TODO": ("GET", "DELETE", "PATCH"),
360 "pnfd_content": {"TODO": ("GET", "PUT")},
361 },
362 },
363 "subscriptions": {
364 "TODO": ("GET", "POST"),
365 "<ID>": {"TODO": ("GET", "DELETE")},
366 },
tierno701018c2019-06-25 11:13:14 +0000367 }
368 },
369 "vnfpkgm": {
370 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100371 "vnf_packages_content": {
372 "METHODS": ("GET", "POST"),
373 "ROLE_PERMISSION": "vnfds:",
374 "<ID>": {
375 "METHODS": ("GET", "PUT", "DELETE"),
376 "ROLE_PERMISSION": "vnfds:id:",
377 },
378 },
379 "vnf_packages": {
380 "METHODS": ("GET", "POST"),
381 "ROLE_PERMISSION": "vnfds:",
382 "<ID>": {
383 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
384 "ROLE_PERMISSION": "vnfds:id:",
385 "package_content": {
386 "METHODS": ("GET", "PUT"), # package
387 "ROLE_PERMISSION": "vnfds:id:",
388 "upload_from_uri": {
389 "METHODS": (),
390 "TODO": ("POST",),
391 "ROLE_PERMISSION": "vnfds:id:upload:",
392 },
393 },
394 "vnfd": {
395 "METHODS": ("GET",), # descriptor inside package
396 "ROLE_PERMISSION": "vnfds:id:content:",
397 },
398 "artifacts": {
399 "METHODS": ("GET",),
400 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
401 "*": None,
402 },
403 "action": {
404 "METHODS": ("POST",),
405 "ROLE_PERMISSION": "vnfds:id:action:",
406 },
407 },
408 },
409 "subscriptions": {
410 "TODO": ("GET", "POST"),
411 "<ID>": {"TODO": ("GET", "DELETE")},
412 },
413 "vnfpkg_op_occs": {
414 "METHODS": ("GET",),
415 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
416 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"},
417 },
tierno701018c2019-06-25 11:13:14 +0000418 }
419 },
420 "nslcm": {
421 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100422 "ns_instances_content": {
423 "METHODS": ("GET", "POST"),
424 "ROLE_PERMISSION": "ns_instances:",
425 "<ID>": {
426 "METHODS": ("GET", "DELETE"),
427 "ROLE_PERMISSION": "ns_instances:id:",
428 },
429 },
430 "ns_instances": {
431 "METHODS": ("GET", "POST"),
432 "ROLE_PERMISSION": "ns_instances:",
433 "<ID>": {
434 "METHODS": ("GET", "DELETE"),
435 "ROLE_PERMISSION": "ns_instances:id:",
garciadeblas0964edf2022-02-11 00:43:44 +0100436 "heal": {
437 "METHODS": ("POST",),
438 "ROLE_PERMISSION": "ns_instances:id:heal:",
439 },
garciadeblas4568a372021-03-24 09:19:48 +0100440 "scale": {
441 "METHODS": ("POST",),
442 "ROLE_PERMISSION": "ns_instances:id:scale:",
443 },
444 "terminate": {
445 "METHODS": ("POST",),
446 "ROLE_PERMISSION": "ns_instances:id:terminate:",
447 },
448 "instantiate": {
449 "METHODS": ("POST",),
450 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
451 },
elumalai8e3806c2022-04-28 17:26:24 +0530452 "migrate": {
453 "METHODS": ("POST",),
454 "ROLE_PERMISSION": "ns_instances:id:migrate:",
455 },
garciadeblas4568a372021-03-24 09:19:48 +0100456 "action": {
457 "METHODS": ("POST",),
458 "ROLE_PERMISSION": "ns_instances:id:action:",
459 },
aticig544a2ae2022-04-05 09:00:17 +0300460 "update": {
461 "METHODS": ("POST",),
462 "ROLE_PERMISSION": "ns_instances:id:update:",
463 },
govindarajul519da482022-04-29 19:05:22 +0530464 "verticalscale": {
465 "METHODS": ("POST",),
garciadeblasf2af4a12023-01-24 16:56:54 +0100466 "ROLE_PERMISSION": "ns_instances:id:verticalscale:",
467 },
garciadeblas4568a372021-03-24 09:19:48 +0100468 },
469 },
470 "ns_lcm_op_occs": {
471 "METHODS": ("GET",),
472 "ROLE_PERMISSION": "ns_instances:opps:",
473 "<ID>": {
474 "METHODS": ("GET",),
475 "ROLE_PERMISSION": "ns_instances:opps:id:",
476 },
477 },
478 "vnfrs": {
479 "METHODS": ("GET",),
480 "ROLE_PERMISSION": "vnf_instances:",
481 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
482 },
483 "vnf_instances": {
484 "METHODS": ("GET",),
485 "ROLE_PERMISSION": "vnf_instances:",
486 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
487 },
488 "subscriptions": {
489 "METHODS": ("GET", "POST"),
490 "ROLE_PERMISSION": "ns_subscriptions:",
491 "<ID>": {
492 "METHODS": ("GET", "DELETE"),
493 "ROLE_PERMISSION": "ns_subscriptions:id:",
494 },
495 },
tierno701018c2019-06-25 11:13:14 +0000496 }
497 },
almagiae47b9132022-05-17 14:12:22 +0200498 "vnflcm": {
499 "v1": {
garciadeblasf2af4a12023-01-24 16:56:54 +0100500 "vnf_instances": {
501 "METHODS": ("GET", "POST"),
502 "ROLE_PERMISSION": "vnflcm_instances:",
503 "<ID>": {
504 "METHODS": ("GET", "DELETE"),
505 "ROLE_PERMISSION": "vnflcm_instances:id:",
506 "scale": {
507 "METHODS": ("POST",),
508 "ROLE_PERMISSION": "vnflcm_instances:id:scale:",
509 },
510 "terminate": {
511 "METHODS": ("POST",),
512 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:",
513 },
514 "instantiate": {
515 "METHODS": ("POST",),
516 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:",
517 },
518 },
519 },
520 "vnf_lcm_op_occs": {
521 "METHODS": ("GET",),
522 "ROLE_PERMISSION": "vnf_instances:opps:",
523 "<ID>": {
524 "METHODS": ("GET",),
525 "ROLE_PERMISSION": "vnf_instances:opps:id:",
526 },
527 },
528 "subscriptions": {
529 "METHODS": ("GET", "POST"),
530 "ROLE_PERMISSION": "vnflcm_subscriptions:",
531 "<ID>": {
532 "METHODS": ("GET", "DELETE"),
533 "ROLE_PERMISSION": "vnflcm_subscriptions:id:",
534 },
535 },
almagiae47b9132022-05-17 14:12:22 +0200536 }
537 },
tierno701018c2019-06-25 11:13:14 +0000538 "nst": {
539 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100540 "netslice_templates_content": {
541 "METHODS": ("GET", "POST"),
542 "ROLE_PERMISSION": "slice_templates:",
543 "<ID>": {
544 "METHODS": ("GET", "PUT", "DELETE"),
545 "ROLE_PERMISSION": "slice_templates:id:",
546 },
547 },
548 "netslice_templates": {
549 "METHODS": ("GET", "POST"),
550 "ROLE_PERMISSION": "slice_templates:",
551 "<ID>": {
552 "METHODS": ("GET", "DELETE"),
553 "TODO": ("PATCH",),
554 "ROLE_PERMISSION": "slice_templates:id:",
555 "nst_content": {
556 "METHODS": ("GET", "PUT"),
557 "ROLE_PERMISSION": "slice_templates:id:content:",
558 },
559 "nst": {
560 "METHODS": ("GET",), # descriptor inside package
561 "ROLE_PERMISSION": "slice_templates:id:content:",
562 },
563 "artifacts": {
564 "METHODS": ("GET",),
565 "ROLE_PERMISSION": "slice_templates:id:content:",
566 "*": None,
567 },
568 },
569 },
570 "subscriptions": {
571 "TODO": ("GET", "POST"),
572 "<ID>": {"TODO": ("GET", "DELETE")},
573 },
tierno701018c2019-06-25 11:13:14 +0000574 }
575 },
576 "nsilcm": {
577 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100578 "netslice_instances_content": {
579 "METHODS": ("GET", "POST"),
580 "ROLE_PERMISSION": "slice_instances:",
581 "<ID>": {
582 "METHODS": ("GET", "DELETE"),
583 "ROLE_PERMISSION": "slice_instances:id:",
584 },
585 },
586 "netslice_instances": {
587 "METHODS": ("GET", "POST"),
588 "ROLE_PERMISSION": "slice_instances:",
589 "<ID>": {
590 "METHODS": ("GET", "DELETE"),
591 "ROLE_PERMISSION": "slice_instances:id:",
592 "terminate": {
593 "METHODS": ("POST",),
594 "ROLE_PERMISSION": "slice_instances:id:terminate:",
595 },
596 "instantiate": {
597 "METHODS": ("POST",),
598 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
599 },
600 "action": {
601 "METHODS": ("POST",),
602 "ROLE_PERMISSION": "slice_instances:id:action:",
603 },
604 },
605 },
606 "nsi_lcm_op_occs": {
607 "METHODS": ("GET",),
608 "ROLE_PERMISSION": "slice_instances:opps:",
609 "<ID>": {
610 "METHODS": ("GET",),
611 "ROLE_PERMISSION": "slice_instances:opps:id:",
612 },
613 },
tierno701018c2019-06-25 11:13:14 +0000614 }
615 },
616 "nspm": {
617 "v1": {
618 "pm_jobs": {
619 "<ID>": {
620 "reports": {
garciadeblas4568a372021-03-24 09:19:48 +0100621 "<ID>": {
622 "METHODS": ("GET",),
623 "ROLE_PERMISSION": "reports:id:",
624 }
tierno701018c2019-06-25 11:13:14 +0000625 }
626 },
627 },
628 },
629 },
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000630 "nsfm": {
631 "v1": {
garciadeblasf2af4a12023-01-24 16:56:54 +0100632 "alarms": {
633 "METHODS": ("GET", "PATCH"),
634 "ROLE_PERMISSION": "alarms:",
635 "<ID>": {
636 "METHODS": ("GET", "PATCH"),
637 "ROLE_PERMISSION": "alarms:id:",
638 },
639 }
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000640 },
641 },
tierno701018c2019-06-25 11:13:14 +0000642}
643
tiernoc94c3df2018-02-09 15:38:54 +0100644
645class NbiException(Exception):
tiernoc94c3df2018-02-09 15:38:54 +0100646 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
647 Exception.__init__(self, message)
648 self.http_code = http_code
649
650
651class Server(object):
652 instance = 0
653 # to decode bytes to str
654 reader = getreader("utf-8")
655
656 def __init__(self):
657 self.instance += 1
tierno701018c2019-06-25 11:13:14 +0000658 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
delacruzramoad682a52019-12-10 16:26:34 +0100659 self.engine = Engine(self.authenticator)
tiernoc94c3df2018-02-09 15:38:54 +0100660
tiernoc94c3df2018-02-09 15:38:54 +0100661 def _format_in(self, kwargs):
garciadeblasf2af4a12023-01-24 16:56:54 +0100662 error_text = "" # error_text must be initialized outside try
tiernoc94c3df2018-02-09 15:38:54 +0100663 try:
664 indata = None
665 if cherrypy.request.body.length:
666 error_text = "Invalid input format "
667
668 if "Content-Type" in cherrypy.request.headers:
669 if "application/json" in cherrypy.request.headers["Content-Type"]:
670 error_text = "Invalid json format "
671 indata = json.load(self.reader(cherrypy.request.body))
gcalvinode4adfe2018-10-30 11:46:09 +0100672 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100673 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
674 error_text = "Invalid yaml format "
garciadeblas4cd875d2023-02-14 19:05:34 +0100675 indata = yaml.safe_load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100676 cherrypy.request.headers.pop("Content-File-MD5", None)
garciadeblas4568a372021-03-24 09:19:48 +0100677 elif (
678 "application/binary" in cherrypy.request.headers["Content-Type"]
679 or "application/gzip"
680 in cherrypy.request.headers["Content-Type"]
681 or "application/zip" in cherrypy.request.headers["Content-Type"]
682 or "text/plain" in cherrypy.request.headers["Content-Type"]
683 ):
tiernof27c79b2018-03-12 17:08:42 +0100684 indata = cherrypy.request.body # .read()
garciadeblas4568a372021-03-24 09:19:48 +0100685 elif (
686 "multipart/form-data"
687 in cherrypy.request.headers["Content-Type"]
688 ):
tiernoc94c3df2018-02-09 15:38:54 +0100689 if "descriptor_file" in kwargs:
690 filecontent = kwargs.pop("descriptor_file")
691 if not filecontent.file:
garciadeblas4568a372021-03-24 09:19:48 +0100692 raise NbiException(
693 "empty file or content", HTTPStatus.BAD_REQUEST
694 )
tiernof27c79b2018-03-12 17:08:42 +0100695 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100696 if filecontent.content_type.value:
garciadeblas4568a372021-03-24 09:19:48 +0100697 cherrypy.request.headers[
698 "Content-Type"
699 ] = filecontent.content_type.value
tiernoc94c3df2018-02-09 15:38:54 +0100700 else:
701 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
702 # "Only 'Content-Type' of type 'application/json' or
703 # 'application/yaml' for input format are available")
704 error_text = "Invalid yaml format "
garciadeblas4cd875d2023-02-14 19:05:34 +0100705 indata = yaml.safe_load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100706 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100707 else:
708 error_text = "Invalid yaml format "
garciadeblas4cd875d2023-02-14 19:05:34 +0100709 indata = yaml.safe_load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100710 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100711 if not indata:
712 indata = {}
713
tiernoc94c3df2018-02-09 15:38:54 +0100714 format_yaml = False
715 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
716 format_yaml = True
717
718 for k, v in kwargs.items():
719 if isinstance(v, str):
720 if v == "":
721 kwargs[k] = None
722 elif format_yaml:
723 try:
garciadeblas4cd875d2023-02-14 19:05:34 +0100724 kwargs[k] = yaml.safe_load(v)
tiernoe1281182018-05-22 12:24:36 +0200725 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100726 pass
garciadeblas4568a372021-03-24 09:19:48 +0100727 elif (
728 k.endswith(".gt")
729 or k.endswith(".lt")
730 or k.endswith(".gte")
731 or k.endswith(".lte")
732 ):
tiernoc94c3df2018-02-09 15:38:54 +0100733 try:
734 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200735 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100736 try:
737 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200738 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100739 pass
740 elif v.find(",") > 0:
741 kwargs[k] = v.split(",")
742 elif isinstance(v, (list, tuple)):
743 for index in range(0, len(v)):
744 if v[index] == "":
745 v[index] = None
746 elif format_yaml:
747 try:
garciadeblas4cd875d2023-02-14 19:05:34 +0100748 v[index] = yaml.safe_load(v[index])
tiernoe1281182018-05-22 12:24:36 +0200749 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100750 pass
751
tiernof27c79b2018-03-12 17:08:42 +0100752 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100753 except (ValueError, yaml.YAMLError) as exc:
754 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
755 except KeyError as exc:
garciadeblas4568a372021-03-24 09:19:48 +0100756 raise NbiException(
757 "Query string error: " + str(exc), HTTPStatus.BAD_REQUEST
758 )
tiernob92094f2018-05-11 13:44:22 +0200759 except Exception as exc:
760 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100761
762 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000763 def _format_out(data, token_info=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100764 """
765 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100766 :param data: response to be sent. Can be a dict, text or file
tierno701018c2019-06-25 11:13:14 +0000767 :param token_info: Contains among other username and project
tierno5792d7d2019-08-30 15:37:12 +0000768 :param _format: The format to be set as Content-Type if data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100769 :return: None
770 """
tierno0f98af52018-03-19 10:28:22 +0100771 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100772 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100773 if accept and "text/html" in accept:
garciadeblas4568a372021-03-24 09:19:48 +0100774 return html.format(
775 data, cherrypy.request, cherrypy.response, token_info
776 )
tierno09c073e2018-04-26 13:36:48 +0200777 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100778 return
779 elif hasattr(data, "read"): # file object
780 if _format:
781 cherrypy.response.headers["Content-Type"] = _format
782 elif "b" in data.mode: # binariy asssumig zip
garciadeblas4568a372021-03-24 09:19:48 +0100783 cherrypy.response.headers["Content-Type"] = "application/zip"
tiernof27c79b2018-03-12 17:08:42 +0100784 else:
garciadeblas4568a372021-03-24 09:19:48 +0100785 cherrypy.response.headers["Content-Type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100786 # TODO check that cherrypy close file. If not implement pending things to close per thread next
787 return data
tierno0f98af52018-03-19 10:28:22 +0100788 if accept:
Frank Bryden02e700c2020-06-03 13:34:16 +0000789 if "text/html" in accept:
garciadeblas4568a372021-03-24 09:19:48 +0100790 return html.format(
791 data, cherrypy.request, cherrypy.response, token_info
792 )
Frank Brydenb5422da2020-08-10 11:44:11 +0000793 elif "application/yaml" in accept or "*/*" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100794 pass
garciadeblas4568a372021-03-24 09:19:48 +0100795 elif "application/json" in accept or (
796 cherrypy.response.status and cherrypy.response.status >= 300
797 ):
798 cherrypy.response.headers[
799 "Content-Type"
800 ] = "application/json; charset=utf-8"
Frank Bryden02e700c2020-06-03 13:34:16 +0000801 a = json.dumps(data, indent=4) + "\n"
garciadeblas4568a372021-03-24 09:19:48 +0100802 return a.encode("utf8")
803 cherrypy.response.headers["Content-Type"] = "application/yaml"
804 return yaml.safe_dump(
805 data,
806 explicit_start=True,
807 indent=4,
808 default_flow_style=False,
809 tags=False,
810 encoding="utf-8",
811 allow_unicode=True,
812 ) # , canonical=True, default_style='"'
tiernoc94c3df2018-02-09 15:38:54 +0100813
814 @cherrypy.expose
815 def index(self, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000816 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100817 try:
818 if cherrypy.request.method == "GET":
tierno701018c2019-06-25 11:13:14 +0000819 token_info = self.authenticator.authorize()
garciadeblas4568a372021-03-24 09:19:48 +0100820 outdata = token_info # Home page
tiernoc94c3df2018-02-09 15:38:54 +0100821 else:
garciadeblas4568a372021-03-24 09:19:48 +0100822 raise cherrypy.HTTPError(
823 HTTPStatus.METHOD_NOT_ALLOWED.value,
824 "Method {} not allowed for tokens".format(cherrypy.request.method),
825 )
tiernoc94c3df2018-02-09 15:38:54 +0100826
tierno701018c2019-06-25 11:13:14 +0000827 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100828
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100829 except (EngineException, AuthException) as e:
tierno701018c2019-06-25 11:13:14 +0000830 # cherrypy.log("index Exception {}".format(e))
tiernoc94c3df2018-02-09 15:38:54 +0100831 cherrypy.response.status = e.http_code.value
tierno701018c2019-06-25 11:13:14 +0000832 return self._format_out("Welcome to OSM!", token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100833
834 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200835 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200836 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200837 try:
838 if cherrypy.request.method != "GET":
garciadeblas4568a372021-03-24 09:19:48 +0100839 raise NbiException(
840 "Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED
841 )
tierno55945e72018-04-06 16:40:27 +0200842 elif args or kwargs:
garciadeblas4568a372021-03-24 09:19:48 +0100843 raise NbiException(
844 "Invalid URL or query string for version",
845 HTTPStatus.METHOD_NOT_ALLOWED,
846 )
tierno9c630112019-08-29 14:21:41 +0000847 # TODO include version of other modules, pick up from some kafka admin message
tierno5792d7d2019-08-30 15:37:12 +0000848 osm_nbi_version = {"version": nbi_version, "date": nbi_version_date}
849 return self._format_out(osm_nbi_version)
tierno55945e72018-04-06 16:40:27 +0200850 except NbiException as e:
851 cherrypy.response.status = e.http_code.value
852 problem_details = {
853 "code": e.http_code.name,
854 "status": e.http_code.value,
855 "detail": str(e),
856 }
857 return self._format_out(problem_details, None)
858
tierno12eac3c2020-03-19 23:22:08 +0000859 def domain(self):
860 try:
861 domains = {
garciadeblas4568a372021-03-24 09:19:48 +0100862 "user_domain_name": cherrypy.tree.apps["/osm"]
863 .config["authentication"]
864 .get("user_domain_name"),
865 "project_domain_name": cherrypy.tree.apps["/osm"]
866 .config["authentication"]
867 .get("project_domain_name"),
868 }
tierno12eac3c2020-03-19 23:22:08 +0000869 return self._format_out(domains)
870 except NbiException as e:
871 cherrypy.response.status = e.http_code.value
872 problem_details = {
873 "code": e.http_code.name,
874 "status": e.http_code.value,
875 "detail": str(e),
876 }
877 return self._format_out(problem_details, None)
878
tiernoa5035702019-07-29 08:54:42 +0000879 @staticmethod
880 def _format_login(token_info):
881 """
882 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
883 log this information
884 :param token_info: Dictionary with token content
885 :return: None
886 """
887 cherrypy.request.login = token_info.get("username", "-")
888 if token_info.get("project_name"):
889 cherrypy.request.login += "/" + token_info["project_name"]
890 if token_info.get("id"):
891 cherrypy.request.login += ";session=" + token_info["id"][0:12]
892
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000893 # NS Fault Management
894 @cherrypy.expose
garciadeblasf2af4a12023-01-24 16:56:54 +0100895 def nsfm(
896 self,
897 version=None,
898 topic=None,
899 uuid=None,
900 project_name=None,
901 ns_id=None,
902 *args,
903 **kwargs
904 ):
905 if topic == "alarms":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000906 try:
907 method = cherrypy.request.method
garciadeblasf2af4a12023-01-24 16:56:54 +0100908 role_permission = self._check_valid_url_method(
909 method, "nsfm", version, topic, None, None, *args
910 )
911 query_string_operations = self._extract_query_string_operations(
912 kwargs, method
913 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000914
garciadeblasf2af4a12023-01-24 16:56:54 +0100915 self.authenticator.authorize(
916 role_permission, query_string_operations, None
917 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000918
919 # to handle get request
garciadeblasf2af4a12023-01-24 16:56:54 +0100920 if cherrypy.request.method == "GET":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000921 # if request is on basis of uuid
garciadeblasf2af4a12023-01-24 16:56:54 +0100922 if uuid and uuid != "None":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000923 try:
924 alarm = self.engine.db.get_one("alarms", {"uuid": uuid})
garciadeblasf2af4a12023-01-24 16:56:54 +0100925 alarm_action = self.engine.db.get_one(
926 "alarms_action", {"uuid": uuid}
927 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000928 alarm.update(alarm_action)
garciadeblasf2af4a12023-01-24 16:56:54 +0100929 vnf = self.engine.db.get_one(
930 "vnfrs", {"nsr-id-ref": alarm["tags"]["ns_id"]}
931 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000932 alarm["vnf-id"] = vnf["_id"]
933 return self._format_out(str(alarm))
934 except Exception:
935 return self._format_out("Please provide valid alarm uuid")
garciadeblasf2af4a12023-01-24 16:56:54 +0100936 elif ns_id and ns_id != "None":
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000937 # if request is on basis of ns_id
938 try:
garciadeblasf2af4a12023-01-24 16:56:54 +0100939 alarms = self.engine.db.get_list(
940 "alarms", {"tags.ns_id": ns_id}
941 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000942 for alarm in alarms:
garciadeblasf2af4a12023-01-24 16:56:54 +0100943 alarm_action = self.engine.db.get_one(
944 "alarms_action", {"uuid": alarm["uuid"]}
945 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000946 alarm.update(alarm_action)
947 return self._format_out(str(alarms))
948 except Exception:
949 return self._format_out("Please provide valid ns id")
950 else:
951 # to return only alarm which are related to given project
garciadeblasf2af4a12023-01-24 16:56:54 +0100952 project = self.engine.db.get_one(
953 "projects", {"name": project_name}
954 )
955 project_id = project.get("_id")
956 ns_list = self.engine.db.get_list(
957 "nsrs", {"_admin.projects_read": project_id}
958 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000959 ns_ids = []
960 for ns in ns_list:
961 ns_ids.append(ns.get("_id"))
962 alarms = self.engine.db.get_list("alarms")
garciadeblasf2af4a12023-01-24 16:56:54 +0100963 alarm_list = [
964 alarm
965 for alarm in alarms
966 if alarm["tags"]["ns_id"] in ns_ids
967 ]
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000968 for alrm in alarm_list:
garciadeblasf2af4a12023-01-24 16:56:54 +0100969 action = self.engine.db.get_one(
970 "alarms_action", {"uuid": alrm.get("uuid")}
971 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000972 alrm.update(action)
973 return self._format_out(str(alarm_list))
974 # to handle patch request for alarm update
garciadeblasf2af4a12023-01-24 16:56:54 +0100975 elif cherrypy.request.method == "PATCH":
garciadeblas4cd875d2023-02-14 19:05:34 +0100976 data = yaml.safe_load(cherrypy.request.body)
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000977 try:
978 # check if uuid is valid
979 self.engine.db.get_one("alarms", {"uuid": data.get("uuid")})
980 except Exception:
981 return self._format_out("Please provide valid alarm uuid.")
982 if data.get("is_enable") is not None:
983 if data.get("is_enable"):
garciadeblasf2af4a12023-01-24 16:56:54 +0100984 alarm_status = "ok"
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000985 else:
garciadeblasf2af4a12023-01-24 16:56:54 +0100986 alarm_status = "disabled"
987 self.engine.db.set_one(
988 "alarms",
989 {"uuid": data.get("uuid")},
990 {"alarm_status": alarm_status},
991 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000992 else:
garciadeblasf2af4a12023-01-24 16:56:54 +0100993 self.engine.db.set_one(
994 "alarms",
995 {"uuid": data.get("uuid")},
996 {"threshold": data.get("threshold")},
997 )
Atul Agarwalb6480fc2021-03-18 08:19:32 +0000998 return self._format_out("Alarm updated")
999 except Exception as e:
garciadeblasf2af4a12023-01-24 16:56:54 +01001000 if isinstance(
1001 e,
1002 (
1003 NbiException,
1004 EngineException,
1005 DbException,
1006 FsException,
1007 MsgException,
1008 AuthException,
1009 ValidationError,
1010 AuthconnException,
1011 ),
1012 ):
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001013 http_code_value = cherrypy.response.status = e.http_code.value
1014 http_code_name = e.http_code.name
1015 cherrypy.log("Exception {}".format(e))
1016 else:
garciadeblasf2af4a12023-01-24 16:56:54 +01001017 http_code_value = (
1018 cherrypy.response.status
1019 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Atul Agarwalb6480fc2021-03-18 08:19:32 +00001020 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
1021 http_code_name = HTTPStatus.BAD_REQUEST.name
1022 problem_details = {
1023 "code": http_code_name,
1024 "status": http_code_value,
1025 "detail": str(e),
1026 }
1027 return self._format_out(problem_details)
1028
tierno55945e72018-04-06 16:40:27 +02001029 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +01001030 def token(self, method, token_id=None, kwargs=None):
tierno701018c2019-06-25 11:13:14 +00001031 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +01001032 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +01001033 indata = self._format_in(kwargs)
1034 if not isinstance(indata, dict):
garciadeblas4568a372021-03-24 09:19:48 +01001035 raise NbiException(
1036 "Expected application/yaml or application/json Content-Type",
1037 HTTPStatus.BAD_REQUEST,
1038 )
tiernoa5035702019-07-29 08:54:42 +00001039
1040 if method == "GET":
1041 token_info = self.authenticator.authorize()
1042 # for logging
1043 self._format_login(token_info)
1044 if token_id:
1045 outdata = self.authenticator.get_token(token_info, token_id)
tiernoc94c3df2018-02-09 15:38:54 +01001046 else:
tiernoa5035702019-07-29 08:54:42 +00001047 outdata = self.authenticator.get_token_list(token_info)
1048 elif method == "POST":
1049 try:
1050 token_info = self.authenticator.authorize()
1051 except Exception:
1052 token_info = None
1053 if kwargs:
1054 indata.update(kwargs)
1055 # This is needed to log the user when authentication fails
1056 cherrypy.request.login = "{}".format(indata.get("username", "-"))
garciadeblas4568a372021-03-24 09:19:48 +01001057 outdata = token_info = self.authenticator.new_token(
1058 token_info, indata, cherrypy.request.remote
1059 )
garciadeblasf2af4a12023-01-24 16:56:54 +01001060 cherrypy.session["Authorization"] = outdata["_id"] # pylint: disable=E1101
tiernoa5035702019-07-29 08:54:42 +00001061 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
1062 # for logging
1063 self._format_login(token_info)
selvi.ja9a1fc82022-04-04 06:54:30 +00001064 # password expiry check
1065 if self.authenticator.check_password_expiry(outdata):
garciadeblasf2af4a12023-01-24 16:56:54 +01001066 outdata = {
1067 "id": outdata["id"],
1068 "message": "change_password",
1069 "user_id": outdata["user_id"],
1070 }
tiernoa5035702019-07-29 08:54:42 +00001071 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1072 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1073 elif method == "DELETE":
1074 if not token_id and "id" in kwargs:
1075 token_id = kwargs["id"]
1076 elif not token_id:
1077 token_info = self.authenticator.authorize()
1078 # for logging
1079 self._format_login(token_info)
1080 token_id = token_info["_id"]
1081 outdata = self.authenticator.del_token(token_id)
1082 token_info = None
garciadeblasf2af4a12023-01-24 16:56:54 +01001083 cherrypy.session["Authorization"] = "logout" # pylint: disable=E1101
tiernoa5035702019-07-29 08:54:42 +00001084 # cherrypy.response.cookie["Authorization"] = token_id
1085 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1086 else:
garciadeblas4568a372021-03-24 09:19:48 +01001087 raise NbiException(
1088 "Method {} not allowed for token".format(method),
1089 HTTPStatus.METHOD_NOT_ALLOWED,
1090 )
tiernoa5035702019-07-29 08:54:42 +00001091 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001092
1093 @cherrypy.expose
1094 def test(self, *args, **kwargs):
garciadeblas4568a372021-03-24 09:19:48 +01001095 if not cherrypy.config.get("server.enable_test") or (
1096 isinstance(cherrypy.config["server.enable_test"], str)
1097 and cherrypy.config["server.enable_test"].lower() == "false"
1098 ):
tierno4836bac2020-01-15 14:41:48 +00001099 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value
1100 return "test URL is disabled"
tiernoc94c3df2018-02-09 15:38:54 +01001101 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +01001102 if args and args[0] == "help":
garciadeblas4568a372021-03-24 09:19:48 +01001103 return (
1104 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1105 "sleep/<time>\nmessage/topic\n</pre></html>"
1106 )
tiernof27c79b2018-03-12 17:08:42 +01001107
1108 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +01001109 try:
1110 # self.engine.load_dbase(cherrypy.request.app.config)
garciadeblasf2af4a12023-01-24 16:56:54 +01001111 pid = self.authenticator.create_admin_project()
1112 self.authenticator.create_admin_user(pid)
tiernoc94c3df2018-02-09 15:38:54 +01001113 return "Done. User 'admin', password 'admin' created"
1114 except Exception:
1115 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1116 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +01001117 elif args and args[0] == "file":
garciadeblas4568a372021-03-24 09:19:48 +01001118 return cherrypy.lib.static.serve_file(
1119 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1],
1120 "text/plain",
1121 "attachment",
1122 )
tiernof27c79b2018-03-12 17:08:42 +01001123 elif args and args[0] == "file2":
garciadeblas4568a372021-03-24 09:19:48 +01001124 f_path = (
1125 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1]
1126 )
tiernof27c79b2018-03-12 17:08:42 +01001127 f = open(f_path, "r")
1128 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +01001129 return f
tierno55945e72018-04-06 16:40:27 +02001130
tiernof27c79b2018-03-12 17:08:42 +01001131 elif len(args) == 2 and args[0] == "db-clear":
tiernof717cbe2018-12-03 16:35:42 +00001132 deleted_info = self.engine.db.del_list(args[1], kwargs)
1133 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
1134 elif len(args) and args[0] == "fs-clear":
1135 if len(args) >= 2:
1136 folders = (args[1],)
1137 else:
1138 folders = self.engine.fs.dir_ls(".")
1139 for folder in folders:
1140 self.engine.fs.file_delete(folder)
1141 return ",".join(folders) + " folders deleted\n"
tiernoc94c3df2018-02-09 15:38:54 +01001142 elif args and args[0] == "login":
1143 if not cherrypy.request.headers.get("Authorization"):
garciadeblas4568a372021-03-24 09:19:48 +01001144 cherrypy.response.headers[
1145 "WWW-Authenticate"
1146 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
tiernoc94c3df2018-02-09 15:38:54 +01001147 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1148 elif args and args[0] == "login2":
1149 if not cherrypy.request.headers.get("Authorization"):
garciadeblas4568a372021-03-24 09:19:48 +01001150 cherrypy.response.headers[
1151 "WWW-Authenticate"
1152 ] = 'Bearer realm="Access to OSM site"'
tiernoc94c3df2018-02-09 15:38:54 +01001153 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
1154 elif args and args[0] == "sleep":
1155 sleep_time = 5
1156 try:
1157 sleep_time = int(args[1])
1158 except Exception:
1159 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
1160 return self._format_out("Database already initialized")
1161 thread_info = cherrypy.thread_data
1162 print(thread_info)
1163 time.sleep(sleep_time)
1164 # thread_info
1165 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +02001166 main_topic = args[1]
1167 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +01001168 try:
garciadeblas4568a372021-03-24 09:19:48 +01001169 if cherrypy.request.method == "POST":
garciadeblas4cd875d2023-02-14 19:05:34 +01001170 to_send = yaml.safe_load(cherrypy.request.body)
tierno55945e72018-04-06 16:40:27 +02001171 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +02001172 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +02001173 return_text += " {}: {}\n".format(k, v)
garciadeblas4568a372021-03-24 09:19:48 +01001174 elif cherrypy.request.method == "GET":
tierno55945e72018-04-06 16:40:27 +02001175 for k, v in kwargs.items():
garciadeblas4cd875d2023-02-14 19:05:34 +01001176 v_dict = yaml.safe_load(v)
tiernof1509b22020-05-12 14:32:37 +00001177 self.engine.msg.write(main_topic, k, v_dict)
1178 return_text += " {}: {}\n".format(k, v_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001179 except Exception as e:
tierno55945e72018-04-06 16:40:27 +02001180 return_text += "Error: " + str(e)
1181 return_text += "</pre></html>\n"
1182 return return_text
tiernoc94c3df2018-02-09 15:38:54 +01001183
1184 return_text = (
garciadeblas4568a372021-03-24 09:19:48 +01001185 "<html><pre>\nheaders:\n args: {}\n".format(args)
1186 + " kwargs: {}\n".format(kwargs)
1187 + " headers: {}\n".format(cherrypy.request.headers)
1188 + " path_info: {}\n".format(cherrypy.request.path_info)
1189 + " query_string: {}\n".format(cherrypy.request.query_string)
garciadeblasf2af4a12023-01-24 16:56:54 +01001190 + " session: {}\n".format(cherrypy.session) # pylint: disable=E1101
garciadeblas4568a372021-03-24 09:19:48 +01001191 + " cookie: {}\n".format(cherrypy.request.cookie)
1192 + " method: {}\n".format(cherrypy.request.method)
garciadeblasf2af4a12023-01-24 16:56:54 +01001193 + " session: {}\n".format(
1194 cherrypy.session.get("fieldname") # pylint: disable=E1101
1195 )
garciadeblas4568a372021-03-24 09:19:48 +01001196 + " body:\n"
1197 )
tiernoc94c3df2018-02-09 15:38:54 +01001198 return_text += " length: {}\n".format(cherrypy.request.body.length)
1199 if cherrypy.request.body.length:
1200 return_text += " content: {}\n".format(
garciadeblas4568a372021-03-24 09:19:48 +01001201 str(
1202 cherrypy.request.body.read(
1203 int(cherrypy.request.headers.get("Content-Length", 0))
1204 )
1205 )
1206 )
tiernoc94c3df2018-02-09 15:38:54 +01001207 if thread_info:
1208 return_text += "thread: {}\n".format(thread_info)
1209 return_text += "</pre></html>"
1210 return return_text
1211
tierno701018c2019-06-25 11:13:14 +00001212 @staticmethod
1213 def _check_valid_url_method(method, *args):
tiernof27c79b2018-03-12 17:08:42 +01001214 if len(args) < 3:
garciadeblas4568a372021-03-24 09:19:48 +01001215 raise NbiException(
1216 "URL must contain at least 'main_topic/version/topic'",
1217 HTTPStatus.METHOD_NOT_ALLOWED,
1218 )
tiernof27c79b2018-03-12 17:08:42 +01001219
tierno701018c2019-06-25 11:13:14 +00001220 reference = valid_url_methods
tiernof27c79b2018-03-12 17:08:42 +01001221 for arg in args:
1222 if arg is None:
1223 break
1224 if not isinstance(reference, dict):
garciadeblas4568a372021-03-24 09:19:48 +01001225 raise NbiException(
1226 "URL contains unexpected extra items '{}'".format(arg),
1227 HTTPStatus.METHOD_NOT_ALLOWED,
1228 )
tiernof27c79b2018-03-12 17:08:42 +01001229
1230 if arg in reference:
1231 reference = reference[arg]
1232 elif "<ID>" in reference:
1233 reference = reference["<ID>"]
1234 elif "*" in reference:
tierno74b53582020-06-18 10:52:37 +00001235 # if there is content
1236 if reference["*"]:
1237 reference = reference["*"]
tiernof27c79b2018-03-12 17:08:42 +01001238 break
1239 else:
garciadeblas4568a372021-03-24 09:19:48 +01001240 raise NbiException(
1241 "Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED
1242 )
tiernof27c79b2018-03-12 17:08:42 +01001243 if "TODO" in reference and method in reference["TODO"]:
garciadeblas4568a372021-03-24 09:19:48 +01001244 raise NbiException(
1245 "Method {} not supported yet for this URL".format(method),
1246 HTTPStatus.NOT_IMPLEMENTED,
1247 )
tierno2236d202018-05-16 19:05:16 +02001248 elif "METHODS" in reference and method not in reference["METHODS"]:
garciadeblas4568a372021-03-24 09:19:48 +01001249 raise NbiException(
1250 "Method {} not supported for this URL".format(method),
1251 HTTPStatus.METHOD_NOT_ALLOWED,
1252 )
tierno701018c2019-06-25 11:13:14 +00001253 return reference["ROLE_PERMISSION"] + method.lower()
tiernof27c79b2018-03-12 17:08:42 +01001254
1255 @staticmethod
tiernob24258a2018-10-04 18:39:49 +02001256 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +01001257 """
1258 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +02001259 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +01001260 :param version:
tiernob24258a2018-10-04 18:39:49 +02001261 :param topic:
tiernof27c79b2018-03-12 17:08:42 +01001262 :param id:
1263 :return: None
1264 """
1265 # 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 +01001266 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(
1267 main_topic, version, topic, id
1268 )
tiernof27c79b2018-03-12 17:08:42 +01001269 return
1270
tierno65ca36d2019-02-12 19:27:52 +01001271 @staticmethod
tierno701018c2019-06-25 11:13:14 +00001272 def _extract_query_string_operations(kwargs, method):
1273 """
1274
1275 :param kwargs:
1276 :return:
1277 """
1278 query_string_operations = []
1279 if kwargs:
1280 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1281 if qs in kwargs and kwargs[qs].lower() != "false":
1282 query_string_operations.append(qs.lower() + ":" + method.lower())
1283 return query_string_operations
1284
1285 @staticmethod
1286 def _manage_admin_query(token_info, kwargs, method, _id):
tierno65ca36d2019-02-12 19:27:52 +01001287 """
1288 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1289 Check that users has rights to use them and returs the admin_query
tierno701018c2019-06-25 11:13:14 +00001290 :param token_info: token_info rights obtained by token
tierno65ca36d2019-02-12 19:27:52 +01001291 :param kwargs: query string input.
1292 :param method: http method: GET, POSST, PUT, ...
1293 :param _id:
1294 :return: admin_query dictionary with keys:
1295 public: True, False or None
1296 force: True or False
1297 project_id: tuple with projects used for accessing an element
1298 set_project: tuple with projects that a created element will belong to
1299 method: show, list, delete, write
1300 """
garciadeblas4568a372021-03-24 09:19:48 +01001301 admin_query = {
1302 "force": False,
1303 "project_id": (token_info["project_id"],),
1304 "username": token_info["username"],
1305 "admin": token_info["admin"],
1306 "public": None,
1307 "allow_show_user_project_role": token_info["allow_show_user_project_role"],
1308 }
tierno65ca36d2019-02-12 19:27:52 +01001309 if kwargs:
1310 # FORCE
1311 if "FORCE" in kwargs:
garciadeblas4568a372021-03-24 09:19:48 +01001312 if (
1313 kwargs["FORCE"].lower() != "false"
1314 ): # if None or True set force to True
tierno65ca36d2019-02-12 19:27:52 +01001315 admin_query["force"] = True
1316 del kwargs["FORCE"]
1317 # PUBLIC
1318 if "PUBLIC" in kwargs:
garciadeblas4568a372021-03-24 09:19:48 +01001319 if (
1320 kwargs["PUBLIC"].lower() != "false"
1321 ): # if None or True set public to True
tierno65ca36d2019-02-12 19:27:52 +01001322 admin_query["public"] = True
1323 else:
1324 admin_query["public"] = False
1325 del kwargs["PUBLIC"]
1326 # ADMIN
1327 if "ADMIN" in kwargs:
1328 behave_as = kwargs.pop("ADMIN")
1329 if behave_as.lower() != "false":
tierno701018c2019-06-25 11:13:14 +00001330 if not token_info["admin"]:
garciadeblas4568a372021-03-24 09:19:48 +01001331 raise NbiException(
1332 "Only admin projects can use 'ADMIN' query string",
1333 HTTPStatus.UNAUTHORIZED,
1334 )
1335 if (
1336 not behave_as or behave_as.lower() == "true"
1337 ): # convert True, None to empty list
tierno65ca36d2019-02-12 19:27:52 +01001338 admin_query["project_id"] = ()
1339 elif isinstance(behave_as, (list, tuple)):
1340 admin_query["project_id"] = behave_as
garciadeblas4568a372021-03-24 09:19:48 +01001341 else: # isinstance(behave_as, str)
1342 admin_query["project_id"] = (behave_as,)
tierno65ca36d2019-02-12 19:27:52 +01001343 if "SET_PROJECT" in kwargs:
1344 set_project = kwargs.pop("SET_PROJECT")
1345 if not set_project:
1346 admin_query["set_project"] = list(admin_query["project_id"])
1347 else:
1348 if isinstance(set_project, str):
garciadeblas4568a372021-03-24 09:19:48 +01001349 set_project = (set_project,)
tierno65ca36d2019-02-12 19:27:52 +01001350 if admin_query["project_id"]:
1351 for p in set_project:
1352 if p not in admin_query["project_id"]:
garciadeblas4568a372021-03-24 09:19:48 +01001353 raise NbiException(
1354 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1355 "'ADMIN='{p}'".format(p=p),
1356 HTTPStatus.UNAUTHORIZED,
1357 )
tierno65ca36d2019-02-12 19:27:52 +01001358 admin_query["set_project"] = set_project
1359
1360 # PROJECT_READ
1361 # if "PROJECT_READ" in kwargs:
1362 # admin_query["project"] = kwargs.pop("project")
tierno701018c2019-06-25 11:13:14 +00001363 # if admin_query["project"] == token_info["project_id"]:
tierno65ca36d2019-02-12 19:27:52 +01001364 if method == "GET":
1365 if _id:
1366 admin_query["method"] = "show"
1367 else:
1368 admin_query["method"] = "list"
1369 elif method == "DELETE":
1370 admin_query["method"] = "delete"
1371 else:
1372 admin_query["method"] = "write"
1373 return admin_query
1374
tiernoc94c3df2018-02-09 15:38:54 +01001375 @cherrypy.expose
garciadeblas4568a372021-03-24 09:19:48 +01001376 def default(
1377 self,
1378 main_topic=None,
1379 version=None,
1380 topic=None,
1381 _id=None,
1382 item=None,
1383 *args,
1384 **kwargs
1385 ):
tierno701018c2019-06-25 11:13:14 +00001386 token_info = None
tiernof27c79b2018-03-12 17:08:42 +01001387 outdata = None
1388 _format = None
tierno0f98af52018-03-19 10:28:22 +01001389 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +02001390 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +02001391 rollback = []
tierno701018c2019-06-25 11:13:14 +00001392 engine_session = None
tiernoc94c3df2018-02-09 15:38:54 +01001393 try:
tiernob24258a2018-10-04 18:39:49 +02001394 if not main_topic or not version or not topic:
garciadeblas4568a372021-03-24 09:19:48 +01001395 raise NbiException(
1396 "URL must contain at least 'main_topic/version/topic'",
1397 HTTPStatus.METHOD_NOT_ALLOWED,
1398 )
1399 if main_topic not in (
1400 "admin",
1401 "vnfpkgm",
1402 "nsd",
1403 "nslcm",
1404 "pdu",
1405 "nst",
1406 "nsilcm",
1407 "nspm",
almagiae47b9132022-05-17 14:12:22 +02001408 "vnflcm",
garciadeblas4568a372021-03-24 09:19:48 +01001409 ):
1410 raise NbiException(
1411 "URL main_topic '{}' not supported".format(main_topic),
1412 HTTPStatus.METHOD_NOT_ALLOWED,
1413 )
1414 if version != "v1":
1415 raise NbiException(
1416 "URL version '{}' not supported".format(version),
1417 HTTPStatus.METHOD_NOT_ALLOWED,
1418 )
tiernoc94c3df2018-02-09 15:38:54 +01001419
garciadeblas4568a372021-03-24 09:19:48 +01001420 if (
1421 kwargs
1422 and "METHOD" in kwargs
1423 and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1424 ):
tiernof27c79b2018-03-12 17:08:42 +01001425 method = kwargs.pop("METHOD")
1426 else:
1427 method = cherrypy.request.method
tierno65ca36d2019-02-12 19:27:52 +01001428
garciadeblas4568a372021-03-24 09:19:48 +01001429 role_permission = self._check_valid_url_method(
1430 method, main_topic, version, topic, _id, item, *args
1431 )
1432 query_string_operations = self._extract_query_string_operations(
1433 kwargs, method
1434 )
tiernob24258a2018-10-04 18:39:49 +02001435 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +01001436 return self.token(method, _id, kwargs)
garciadeblas4568a372021-03-24 09:19:48 +01001437 token_info = self.authenticator.authorize(
1438 role_permission, query_string_operations, _id
1439 )
tierno12eac3c2020-03-19 23:22:08 +00001440 if main_topic == "admin" and topic == "domains":
1441 return self.domain()
tierno701018c2019-06-25 11:13:14 +00001442 engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
tiernof27c79b2018-03-12 17:08:42 +01001443 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +02001444 engine_topic = topic
preethika.p329b8182020-04-22 12:25:39 +05301445
vijay.r35ef2f72019-04-30 17:55:49 +05301446 if item and topic != "pm_jobs":
tiernob24258a2018-10-04 18:39:49 +02001447 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +01001448
tiernob24258a2018-10-04 18:39:49 +02001449 if main_topic == "nsd":
1450 engine_topic = "nsds"
1451 elif main_topic == "vnfpkgm":
1452 engine_topic = "vnfds"
delacruzramo271d2002019-12-02 21:00:37 +01001453 if topic == "vnfpkg_op_occs":
1454 engine_topic = "vnfpkgops"
1455 if topic == "vnf_packages" and item == "action":
1456 engine_topic = "vnfpkgops"
tiernob24258a2018-10-04 18:39:49 +02001457 elif main_topic == "nslcm":
1458 engine_topic = "nsrs"
1459 if topic == "ns_lcm_op_occs":
1460 engine_topic = "nslcmops"
1461 if topic == "vnfrs" or topic == "vnf_instances":
1462 engine_topic = "vnfrs"
almagiae47b9132022-05-17 14:12:22 +02001463 elif main_topic == "vnflcm":
1464 if topic == "vnf_lcm_op_occs":
1465 engine_topic = "vnflcmops"
garciadeblas9750c5a2018-10-15 16:20:35 +02001466 elif main_topic == "nst":
1467 engine_topic = "nsts"
1468 elif main_topic == "nsilcm":
1469 engine_topic = "nsis"
1470 if topic == "nsi_lcm_op_occs":
delacruzramoc061f562019-04-05 11:00:02 +02001471 engine_topic = "nsilcmops"
tiernob24258a2018-10-04 18:39:49 +02001472 elif main_topic == "pdu":
1473 engine_topic = "pdus"
garciadeblas4568a372021-03-24 09:19:48 +01001474 if (
1475 engine_topic == "vims"
1476 ): # TODO this is for backward compatibility, it will be removed in the future
tiernob24258a2018-10-04 18:39:49 +02001477 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +01001478
preethika.p329b8182020-04-22 12:25:39 +05301479 if topic == "subscriptions":
1480 engine_topic = main_topic + "_" + topic
1481
tiernoc94c3df2018-02-09 15:38:54 +01001482 if method == "GET":
garciadeblas4568a372021-03-24 09:19:48 +01001483 if item in (
1484 "nsd_content",
1485 "package_content",
1486 "artifacts",
1487 "vnfd",
1488 "nsd",
1489 "nst",
1490 "nst_content",
1491 ):
garciadeblas9750c5a2018-10-15 16:20:35 +02001492 if item in ("vnfd", "nsd", "nst"):
tiernof27c79b2018-03-12 17:08:42 +01001493 path = "$DESCRIPTOR"
1494 elif args:
1495 path = args
tiernob24258a2018-10-04 18:39:49 +02001496 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +01001497 path = ()
1498 else:
1499 path = None
garciadeblas4568a372021-03-24 09:19:48 +01001500 file, _format = self.engine.get_file(
1501 engine_session,
1502 engine_topic,
1503 _id,
1504 path,
1505 cherrypy.request.headers.get("Accept"),
1506 )
tiernof27c79b2018-03-12 17:08:42 +01001507 outdata = file
1508 elif not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001509 outdata = self.engine.get_item_list(
1510 engine_session, engine_topic, kwargs, api_req=True
1511 )
tiernoc94c3df2018-02-09 15:38:54 +01001512 else:
vijay.r35ef2f72019-04-30 17:55:49 +05301513 if item == "reports":
1514 # TODO check that project_id (_id in this context) has permissions
1515 _id = args[0]
K Sai Kiran57589552021-01-27 21:38:34 +05301516 filter_q = None
1517 if "vcaStatusRefresh" in kwargs:
1518 filter_q = {"vcaStatusRefresh": kwargs["vcaStatusRefresh"]}
garciadeblasf2af4a12023-01-24 16:56:54 +01001519 outdata = self.engine.get_item(
1520 engine_session, engine_topic, _id, filter_q, True
1521 )
delacruzramo271d2002019-12-02 21:00:37 +01001522
tiernof27c79b2018-03-12 17:08:42 +01001523 elif method == "POST":
tiernobdebce92019-07-01 15:36:49 +00001524 cherrypy.response.status = HTTPStatus.CREATED.value
garciadeblas4568a372021-03-24 09:19:48 +01001525 if topic in (
1526 "ns_descriptors_content",
1527 "vnf_packages_content",
1528 "netslice_templates_content",
1529 ):
tiernof27c79b2018-03-12 17:08:42 +01001530 _id = cherrypy.request.headers.get("Transaction-Id")
1531 if not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001532 _id, _ = self.engine.new_item(
1533 rollback,
1534 engine_session,
1535 engine_topic,
1536 {},
1537 None,
1538 cherrypy.request.headers,
1539 )
1540 completed = self.engine.upload_content(
1541 engine_session,
1542 engine_topic,
1543 _id,
1544 indata,
1545 kwargs,
1546 cherrypy.request.headers,
1547 )
tiernof27c79b2018-03-12 17:08:42 +01001548 if completed:
tiernob24258a2018-10-04 18:39:49 +02001549 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001550 else:
1551 cherrypy.response.headers["Transaction-Id"] = _id
1552 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +02001553 elif topic == "ns_instances_content":
1554 # creates NSR
garciadeblas4568a372021-03-24 09:19:48 +01001555 _id, _ = self.engine.new_item(
1556 rollback, engine_session, engine_topic, indata, kwargs
1557 )
tiernob24258a2018-10-04 18:39:49 +02001558 # creates nslcmop
1559 indata["lcmOperationType"] = "instantiate"
1560 indata["nsInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001561 nslcmop_id, _ = self.engine.new_item(
1562 rollback, engine_session, "nslcmops", indata, None
1563 )
tiernob24258a2018-10-04 18:39:49 +02001564 self._set_location_header(main_topic, version, topic, _id)
kuuse078f55e2019-05-16 19:24:21 +02001565 outdata = {"id": _id, "nslcmop_id": nslcmop_id}
tiernob24258a2018-10-04 18:39:49 +02001566 elif topic == "ns_instances" and item:
1567 indata["lcmOperationType"] = item
1568 indata["nsInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001569 _id, _ = self.engine.new_item(
1570 rollback, engine_session, "nslcmops", indata, kwargs
1571 )
1572 self._set_location_header(
1573 main_topic, version, "ns_lcm_op_occs", _id
1574 )
tierno65acb4d2018-04-06 16:42:40 +02001575 outdata = {"id": _id}
1576 cherrypy.response.status = HTTPStatus.ACCEPTED.value
garciadeblas9750c5a2018-10-15 16:20:35 +02001577 elif topic == "netslice_instances_content":
Felipe Vicens07f31722018-10-29 15:16:44 +01001578 # creates NetSlice_Instance_record (NSIR)
garciadeblas4568a372021-03-24 09:19:48 +01001579 _id, _ = self.engine.new_item(
1580 rollback, engine_session, engine_topic, indata, kwargs
1581 )
Felipe Vicens07f31722018-10-29 15:16:44 +01001582 self._set_location_header(main_topic, version, topic, _id)
garciadeblas9750c5a2018-10-15 16:20:35 +02001583 indata["lcmOperationType"] = "instantiate"
Felipe Vicens126af572019-06-05 19:13:04 +02001584 indata["netsliceInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001585 nsilcmop_id, _ = self.engine.new_item(
1586 rollback, engine_session, "nsilcmops", indata, kwargs
1587 )
kuuse078f55e2019-05-16 19:24:21 +02001588 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
garciadeblas9750c5a2018-10-15 16:20:35 +02001589 elif topic == "netslice_instances" and item:
1590 indata["lcmOperationType"] = item
Felipe Vicens126af572019-06-05 19:13:04 +02001591 indata["netsliceInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001592 _id, _ = self.engine.new_item(
1593 rollback, engine_session, "nsilcmops", indata, kwargs
1594 )
1595 self._set_location_header(
1596 main_topic, version, "nsi_lcm_op_occs", _id
1597 )
garciadeblas9750c5a2018-10-15 16:20:35 +02001598 outdata = {"id": _id}
1599 cherrypy.response.status = HTTPStatus.ACCEPTED.value
delacruzramo271d2002019-12-02 21:00:37 +01001600 elif topic == "vnf_packages" and item == "action":
1601 indata["lcmOperationType"] = item
1602 indata["vnfPkgId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001603 _id, _ = self.engine.new_item(
1604 rollback, engine_session, "vnfpkgops", indata, kwargs
1605 )
1606 self._set_location_header(
1607 main_topic, version, "vnfpkg_op_occs", _id
1608 )
delacruzramo271d2002019-12-02 21:00:37 +01001609 outdata = {"id": _id}
1610 cherrypy.response.status = HTTPStatus.ACCEPTED.value
preethika.p329b8182020-04-22 12:25:39 +05301611 elif topic == "subscriptions":
garciadeblas4568a372021-03-24 09:19:48 +01001612 _id, _ = self.engine.new_item(
1613 rollback, engine_session, engine_topic, indata, kwargs
1614 )
preethika.p329b8182020-04-22 12:25:39 +05301615 self._set_location_header(main_topic, version, topic, _id)
1616 link = {}
1617 link["self"] = cherrypy.response.headers["Location"]
garciadeblas4568a372021-03-24 09:19:48 +01001618 outdata = {
1619 "id": _id,
1620 "filter": indata["filter"],
1621 "callbackUri": indata["CallbackUri"],
1622 "_links": link,
1623 }
preethika.p329b8182020-04-22 12:25:39 +05301624 cherrypy.response.status = HTTPStatus.CREATED.value
almagiae47b9132022-05-17 14:12:22 +02001625 elif topic == "vnf_instances" and item:
1626 indata["lcmOperationType"] = item
1627 indata["vnfInstanceId"] = _id
garciadeblasf2af4a12023-01-24 16:56:54 +01001628 _id, _ = self.engine.new_item(
1629 rollback, engine_session, "vnflcmops", indata, kwargs
1630 )
1631 self._set_location_header(
1632 main_topic, version, "vnf_lcm_op_occs", _id
1633 )
almagiae47b9132022-05-17 14:12:22 +02001634 outdata = {"id": _id}
1635 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +01001636 else:
garciadeblas4568a372021-03-24 09:19:48 +01001637 _id, op_id = self.engine.new_item(
1638 rollback,
1639 engine_session,
1640 engine_topic,
1641 indata,
1642 kwargs,
1643 cherrypy.request.headers,
1644 )
tiernob24258a2018-10-04 18:39:49 +02001645 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001646 outdata = {"id": _id}
tiernobdebce92019-07-01 15:36:49 +00001647 if op_id:
1648 outdata["op_id"] = op_id
1649 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernob24258a2018-10-04 18:39:49 +02001650 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tierno09c073e2018-04-26 13:36:48 +02001651
tiernoc94c3df2018-02-09 15:38:54 +01001652 elif method == "DELETE":
1653 if not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001654 outdata = self.engine.del_item_list(
1655 engine_session, engine_topic, kwargs
1656 )
tierno09c073e2018-04-26 13:36:48 +02001657 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +01001658 else: # len(args) > 1
tierno22577432020-04-08 15:16:57 +00001659 # for NS NSI generate an operation
1660 op_id = None
tierno701018c2019-06-25 11:13:14 +00001661 if topic == "ns_instances_content" and not engine_session["force"]:
tiernob24258a2018-10-04 18:39:49 +02001662 nslcmop_desc = {
1663 "lcmOperationType": "terminate",
1664 "nsInstanceId": _id,
garciadeblas4568a372021-03-24 09:19:48 +01001665 "autoremove": True,
tiernob24258a2018-10-04 18:39:49 +02001666 }
garciadeblas4568a372021-03-24 09:19:48 +01001667 op_id, _ = self.engine.new_item(
1668 rollback, engine_session, "nslcmops", nslcmop_desc, kwargs
1669 )
tierno22577432020-04-08 15:16:57 +00001670 if op_id:
1671 outdata = {"_id": op_id}
garciadeblas4568a372021-03-24 09:19:48 +01001672 elif (
1673 topic == "netslice_instances_content"
1674 and not engine_session["force"]
1675 ):
garciadeblas9750c5a2018-10-15 16:20:35 +02001676 nsilcmop_desc = {
1677 "lcmOperationType": "terminate",
Felipe Vicens126af572019-06-05 19:13:04 +02001678 "netsliceInstanceId": _id,
garciadeblas4568a372021-03-24 09:19:48 +01001679 "autoremove": True,
garciadeblas9750c5a2018-10-15 16:20:35 +02001680 }
garciadeblas4568a372021-03-24 09:19:48 +01001681 op_id, _ = self.engine.new_item(
1682 rollback, engine_session, "nsilcmops", nsilcmop_desc, None
1683 )
tierno22577432020-04-08 15:16:57 +00001684 if op_id:
1685 outdata = {"_id": op_id}
1686 # if there is not any deletion in process, delete
1687 if not op_id:
1688 op_id = self.engine.del_item(engine_session, engine_topic, _id)
1689 if op_id:
1690 outdata = {"op_id": op_id}
garciadeblas4568a372021-03-24 09:19:48 +01001691 cherrypy.response.status = (
1692 HTTPStatus.ACCEPTED.value
1693 if op_id
1694 else HTTPStatus.NO_CONTENT.value
1695 )
tierno09c073e2018-04-26 13:36:48 +02001696
tierno7ae10112018-05-18 14:36:02 +02001697 elif method in ("PUT", "PATCH"):
tiernobdebce92019-07-01 15:36:49 +00001698 op_id = None
tierno701018c2019-06-25 11:13:14 +00001699 if not indata and not kwargs and not engine_session.get("set_project"):
garciadeblas4568a372021-03-24 09:19:48 +01001700 raise NbiException(
1701 "Nothing to update. Provide payload and/or query string",
1702 HTTPStatus.BAD_REQUEST,
1703 )
1704 if (
1705 item in ("nsd_content", "package_content", "nst_content")
1706 and method == "PUT"
1707 ):
1708 completed = self.engine.upload_content(
1709 engine_session,
1710 engine_topic,
1711 _id,
1712 indata,
1713 kwargs,
1714 cherrypy.request.headers,
1715 )
tiernof27c79b2018-03-12 17:08:42 +01001716 if not completed:
1717 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +01001718 else:
garciadeblas4568a372021-03-24 09:19:48 +01001719 op_id = self.engine.edit_item(
1720 engine_session, engine_topic, _id, indata, kwargs
1721 )
tiernobdebce92019-07-01 15:36:49 +00001722
1723 if op_id:
1724 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1725 outdata = {"op_id": op_id}
1726 else:
1727 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
1728 outdata = None
tiernoc94c3df2018-02-09 15:38:54 +01001729 else:
garciadeblas4568a372021-03-24 09:19:48 +01001730 raise NbiException(
1731 "Method {} not allowed".format(method),
1732 HTTPStatus.METHOD_NOT_ALLOWED,
1733 )
tiernoa6bb45d2019-06-14 09:45:39 +00001734
1735 # if Role information changes, it is needed to reload the information of roles
1736 if topic == "roles" and method != "GET":
1737 self.authenticator.load_operation_to_allowed_roles()
delacruzramoad682a52019-12-10 16:26:34 +01001738
garciadeblas4568a372021-03-24 09:19:48 +01001739 if (
1740 topic == "projects"
1741 and method == "DELETE"
1742 or topic in ["users", "roles"]
1743 and method in ["PUT", "PATCH", "DELETE"]
1744 ):
delacruzramoad682a52019-12-10 16:26:34 +01001745 self.authenticator.remove_token_from_cache()
1746
tierno701018c2019-06-25 11:13:14 +00001747 return self._format_out(outdata, token_info, _format)
tiernob24258a2018-10-04 18:39:49 +02001748 except Exception as e:
garciadeblas4568a372021-03-24 09:19:48 +01001749 if isinstance(
1750 e,
1751 (
1752 NbiException,
1753 EngineException,
1754 DbException,
1755 FsException,
1756 MsgException,
1757 AuthException,
1758 ValidationError,
1759 AuthconnException,
1760 ),
1761 ):
tiernob24258a2018-10-04 18:39:49 +02001762 http_code_value = cherrypy.response.status = e.http_code.value
1763 http_code_name = e.http_code.name
1764 cherrypy.log("Exception {}".format(e))
1765 else:
garciadeblas4568a372021-03-24 09:19:48 +01001766 http_code_value = (
1767 cherrypy.response.status
1768 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Felipe Vicense36ab852018-11-23 14:12:09 +01001769 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
tiernob24258a2018-10-04 18:39:49 +02001770 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +02001771 if hasattr(outdata, "close"): # is an open file
1772 outdata.close()
tiernob24258a2018-10-04 18:39:49 +02001773 error_text = str(e)
1774 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +02001775 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +02001776 try:
tiernocc103432018-10-19 14:10:35 +02001777 if rollback_item.get("operation") == "set":
garciadeblas4568a372021-03-24 09:19:48 +01001778 self.engine.db.set_one(
1779 rollback_item["topic"],
1780 {"_id": rollback_item["_id"]},
1781 rollback_item["content"],
1782 fail_on_empty=False,
1783 )
preethika.p329b8182020-04-22 12:25:39 +05301784 elif rollback_item.get("operation") == "del_list":
garciadeblas4568a372021-03-24 09:19:48 +01001785 self.engine.db.del_list(
1786 rollback_item["topic"],
1787 rollback_item["filter"],
garciadeblas4568a372021-03-24 09:19:48 +01001788 )
tiernocc103432018-10-19 14:10:35 +02001789 else:
garciadeblas4568a372021-03-24 09:19:48 +01001790 self.engine.db.del_one(
1791 rollback_item["topic"],
1792 {"_id": rollback_item["_id"]},
1793 fail_on_empty=False,
1794 )
tierno3ace63c2018-05-03 17:51:43 +02001795 except Exception as e2:
garciadeblas4568a372021-03-24 09:19:48 +01001796 rollback_error_text = "Rollback Exception {}: {}".format(
1797 rollback_item, e2
1798 )
tiernob24258a2018-10-04 18:39:49 +02001799 cherrypy.log(rollback_error_text)
1800 error_text += ". " + rollback_error_text
1801 # if isinstance(e, MsgException):
1802 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1803 # engine_topic[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +01001804 problem_details = {
tiernob24258a2018-10-04 18:39:49 +02001805 "code": http_code_name,
1806 "status": http_code_value,
1807 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +01001808 }
tierno701018c2019-06-25 11:13:14 +00001809 return self._format_out(problem_details, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001810 # raise cherrypy.HTTPError(e.http_code.value, str(e))
tiernoa5035702019-07-29 08:54:42 +00001811 finally:
1812 if token_info:
1813 self._format_login(token_info)
1814 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
1815 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1816 if outdata.get(logging_id):
garciadeblas4568a372021-03-24 09:19:48 +01001817 cherrypy.request.login += ";{}={}".format(
1818 logging_id, outdata[logging_id][:36]
1819 )
tiernoc94c3df2018-02-09 15:38:54 +01001820
1821
tiernoc94c3df2018-02-09 15:38:54 +01001822def _start_service():
1823 """
1824 Callback function called when cherrypy.engine starts
1825 Override configuration with env variables
1826 Set database, storage, message configuration
1827 Init database with admin/admin user password
1828 """
tierno932499c2019-01-28 17:28:10 +00001829 global nbi_server
1830 global subscription_thread
tiernoc94c3df2018-02-09 15:38:54 +01001831 cherrypy.log.error("Starting osm_nbi")
1832 # update general cherrypy configuration
1833 update_dict = {}
1834
garciadeblas4568a372021-03-24 09:19:48 +01001835 engine_config = cherrypy.tree.apps["/osm"].config
tiernoc94c3df2018-02-09 15:38:54 +01001836 for k, v in environ.items():
1837 if not k.startswith("OSMNBI_"):
1838 continue
tiernoe1281182018-05-22 12:24:36 +02001839 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +01001840 if not k2:
1841 continue
1842 try:
1843 # update static configuration
garciadeblas4568a372021-03-24 09:19:48 +01001844 if k == "OSMNBI_STATIC_DIR":
1845 engine_config["/static"]["tools.staticdir.dir"] = v
1846 engine_config["/static"]["tools.staticdir.on"] = True
1847 elif k == "OSMNBI_SOCKET_PORT" or k == "OSMNBI_SERVER_PORT":
1848 update_dict["server.socket_port"] = int(v)
1849 elif k == "OSMNBI_SOCKET_HOST" or k == "OSMNBI_SERVER_HOST":
1850 update_dict["server.socket_host"] = v
tiernof5298be2018-05-16 14:43:57 +02001851 elif k1 in ("server", "test", "auth", "log"):
garciadeblas4568a372021-03-24 09:19:48 +01001852 update_dict[k1 + "." + k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001853 elif k1 in ("message", "database", "storage", "authentication"):
tiernof5298be2018-05-16 14:43:57 +02001854 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001855 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +01001856 engine_config[k1][k2] = int(v)
1857 else:
1858 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001859
tiernoc94c3df2018-02-09 15:38:54 +01001860 except ValueError as e:
1861 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1862 except Exception as e:
garciadeblasf2af4a12023-01-24 16:56:54 +01001863 cherrypy.log(
1864 "WARNING: skipping environ '{}' on exception '{}'".format(k, e)
1865 )
tiernoc94c3df2018-02-09 15:38:54 +01001866
1867 if update_dict:
1868 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +02001869 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001870
1871 # logging cherrypy
garciadeblas4568a372021-03-24 09:19:48 +01001872 log_format_simple = (
1873 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1874 )
1875 log_formatter_simple = logging.Formatter(
1876 log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S"
1877 )
tiernoc94c3df2018-02-09 15:38:54 +01001878 logger_server = logging.getLogger("cherrypy.error")
1879 logger_access = logging.getLogger("cherrypy.access")
1880 logger_cherry = logging.getLogger("cherrypy")
1881 logger_nbi = logging.getLogger("nbi")
1882
tiernof5298be2018-05-16 14:43:57 +02001883 if "log.file" in engine_config["global"]:
garciadeblas4568a372021-03-24 09:19:48 +01001884 file_handler = logging.handlers.RotatingFileHandler(
1885 engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0
1886 )
tiernoc94c3df2018-02-09 15:38:54 +01001887 file_handler.setFormatter(log_formatter_simple)
1888 logger_cherry.addHandler(file_handler)
1889 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +02001890 # log always to standard output
garciadeblas4568a372021-03-24 09:19:48 +01001891 for format_, logger in {
1892 "nbi.server %(filename)s:%(lineno)s": logger_server,
1893 "nbi.access %(filename)s:%(lineno)s": logger_access,
1894 "%(name)s %(filename)s:%(lineno)s": logger_nbi,
1895 }.items():
tiernob24258a2018-10-04 18:39:49 +02001896 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
garciadeblas4568a372021-03-24 09:19:48 +01001897 log_formatter_cherry = logging.Formatter(
1898 log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S"
1899 )
tiernob24258a2018-10-04 18:39:49 +02001900 str_handler = logging.StreamHandler()
1901 str_handler.setFormatter(log_formatter_cherry)
1902 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +01001903
tiernof5298be2018-05-16 14:43:57 +02001904 if engine_config["global"].get("log.level"):
1905 logger_cherry.setLevel(engine_config["global"]["log.level"])
1906 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +01001907
1908 # logging other modules
garciadeblas4568a372021-03-24 09:19:48 +01001909 for k1, logname in {
1910 "message": "nbi.msg",
1911 "database": "nbi.db",
1912 "storage": "nbi.fs",
1913 }.items():
tiernoc94c3df2018-02-09 15:38:54 +01001914 engine_config[k1]["logger_name"] = logname
1915 logger_module = logging.getLogger(logname)
1916 if "logfile" in engine_config[k1]:
garciadeblas4568a372021-03-24 09:19:48 +01001917 file_handler = logging.handlers.RotatingFileHandler(
1918 engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0
1919 )
tiernoc94c3df2018-02-09 15:38:54 +01001920 file_handler.setFormatter(log_formatter_simple)
1921 logger_module.addHandler(file_handler)
1922 if "loglevel" in engine_config[k1]:
1923 logger_module.setLevel(engine_config[k1]["loglevel"])
1924 # TODO add more entries, e.g.: storage
garciadeblas4568a372021-03-24 09:19:48 +01001925 cherrypy.tree.apps["/osm"].root.engine.start(engine_config)
1926 cherrypy.tree.apps["/osm"].root.authenticator.start(engine_config)
1927 cherrypy.tree.apps["/osm"].root.engine.init_db(target_version=database_version)
1928 cherrypy.tree.apps["/osm"].root.authenticator.init_db(
1929 target_version=auth_database_version
1930 )
tiernobee508e2019-01-21 11:21:49 +00001931
tierno932499c2019-01-28 17:28:10 +00001932 # start subscriptions thread:
garciadeblas4568a372021-03-24 09:19:48 +01001933 subscription_thread = SubscriptionThread(
1934 config=engine_config, engine=nbi_server.engine
1935 )
tierno932499c2019-01-28 17:28:10 +00001936 subscription_thread.start()
1937 # Do not capture except SubscriptionException
1938
tiernob2e48bd2020-02-04 15:47:18 +00001939 backend = engine_config["authentication"]["backend"]
garciadeblas4568a372021-03-24 09:19:48 +01001940 cherrypy.log.error(
1941 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1942 nbi_version, nbi_version_date, backend
1943 )
1944 )
tiernoc94c3df2018-02-09 15:38:54 +01001945
1946
1947def _stop_service():
1948 """
1949 Callback function called when cherrypy.engine stops
1950 TODO: Ending database connections.
1951 """
tierno932499c2019-01-28 17:28:10 +00001952 global subscription_thread
tierno65ca36d2019-02-12 19:27:52 +01001953 if subscription_thread:
1954 subscription_thread.terminate()
tierno932499c2019-01-28 17:28:10 +00001955 subscription_thread = None
garciadeblas4568a372021-03-24 09:19:48 +01001956 cherrypy.tree.apps["/osm"].root.engine.stop()
tiernoc94c3df2018-02-09 15:38:54 +01001957 cherrypy.log.error("Stopping osm_nbi")
1958
tierno2236d202018-05-16 19:05:16 +02001959
tiernof5298be2018-05-16 14:43:57 +02001960def nbi(config_file):
tierno932499c2019-01-28 17:28:10 +00001961 global nbi_server
tiernoc94c3df2018-02-09 15:38:54 +01001962 # conf = {
1963 # '/': {
1964 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1965 # 'tools.sessions.on': True,
1966 # 'tools.response_headers.on': True,
1967 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1968 # }
1969 # }
1970 # cherrypy.Server.ssl_module = 'builtin'
1971 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1972 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1973 # cherrypy.Server.thread_pool = 10
1974 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1975
1976 # cherrypy.config.update({'tools.auth_basic.on': True,
1977 # 'tools.auth_basic.realm': 'localhost',
1978 # 'tools.auth_basic.checkpassword': validate_password})
tierno932499c2019-01-28 17:28:10 +00001979 nbi_server = Server()
garciadeblas4568a372021-03-24 09:19:48 +01001980 cherrypy.engine.subscribe("start", _start_service)
1981 cherrypy.engine.subscribe("stop", _stop_service)
1982 cherrypy.quickstart(nbi_server, "/osm", config_file)
tiernof5298be2018-05-16 14:43:57 +02001983
1984
1985def usage():
garciadeblas4568a372021-03-24 09:19:48 +01001986 print(
1987 """Usage: {} [options]
tiernof5298be2018-05-16 14:43:57 +02001988 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1989 -h|--help: shows this help
garciadeblas4568a372021-03-24 09:19:48 +01001990 """.format(
1991 sys.argv[0]
1992 )
1993 )
tierno2236d202018-05-16 19:05:16 +02001994 # --log-socket-host HOST: send logs to this host")
1995 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +01001996
1997
garciadeblas4568a372021-03-24 09:19:48 +01001998if __name__ == "__main__":
tiernof5298be2018-05-16 14:43:57 +02001999 try:
2000 # load parameters and configuration
2001 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
2002 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
2003 config_file = None
2004 for o, a in opts:
2005 if o in ("-h", "--help"):
2006 usage()
2007 sys.exit()
2008 elif o in ("-c", "--config"):
2009 config_file = a
2010 # elif o == "--log-socket-port":
2011 # log_socket_port = a
2012 # elif o == "--log-socket-host":
2013 # log_socket_host = a
2014 # elif o == "--log-file":
2015 # log_file = a
2016 else:
2017 assert False, "Unhandled option"
2018 if config_file:
2019 if not path.isfile(config_file):
garciadeblas4568a372021-03-24 09:19:48 +01002020 print(
2021 "configuration file '{}' that not exist".format(config_file),
2022 file=sys.stderr,
2023 )
tiernof5298be2018-05-16 14:43:57 +02002024 exit(1)
2025 else:
garciadeblas4568a372021-03-24 09:19:48 +01002026 for config_file in (
2027 __file__[: __file__.rfind(".")] + ".cfg",
2028 "./nbi.cfg",
2029 "/etc/osm/nbi.cfg",
2030 ):
tiernof5298be2018-05-16 14:43:57 +02002031 if path.isfile(config_file):
2032 break
2033 else:
garciadeblas4568a372021-03-24 09:19:48 +01002034 print(
2035 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
2036 file=sys.stderr,
2037 )
tiernof5298be2018-05-16 14:43:57 +02002038 exit(1)
2039 nbi(config_file)
2040 except getopt.GetoptError as e:
2041 print(str(e), file=sys.stderr)
2042 # usage()
2043 exit(1)