blob: 6c63ad8c82ffac55447a340f77395d0ef89fe4c8 [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
tierno9c630112019-08-29 14:21:41 +000042__version__ = "0.1.3" # file version, not NBI version
43version_date = "Aug 2019"
44
tierno1f029d82019-06-13 22:37:04 +000045database_version = '1.2'
Eduardo Sousa819d34c2018-07-31 01:20:02 +010046auth_database_version = '1.0'
tierno932499c2019-01-28 17:28:10 +000047nbi_server = None # instance of Server class
48subscription_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
88 heal 5
tiernoc94c3df2018-02-09 15:38:54 +010089 /ns_lcm_op_occs 5 5
90 /<nsLcmOpOccId> 5 5 5
91 TO BE COMPLETED 5 5
tiernof759d822018-06-11 18:54:54 +020092 /vnf_instances (also vnfrs for compatibility) O
93 /<vnfInstanceId> O
tiernof27c79b2018-03-12 17:08:42 +010094 /subscriptions 5 5
95 /<subscriptionId> 5 X
garciadeblas9750c5a2018-10-15 16:20:35 +020096
tiernocb83c942018-09-24 17:28:13 +020097 /pdu/v1
tierno032916c2019-03-22 13:27:12 +000098 /pdu_descriptors O O
tiernocb83c942018-09-24 17:28:13 +020099 /<id> O O O O
garciadeblas9750c5a2018-10-15 16:20:35 +0200100
tiernof27c79b2018-03-12 17:08:42 +0100101 /admin/v1
102 /tokens O O
tierno2236d202018-05-16 19:05:16 +0200103 /<id> O O
tiernof27c79b2018-03-12 17:08:42 +0100104 /users O O
tiernocd54a4a2018-09-12 16:40:35 +0200105 /<id> O O O O
tiernof27c79b2018-03-12 17:08:42 +0100106 /projects O O
tierno2236d202018-05-16 19:05:16 +0200107 /<id> O O
tierno55ba2e62018-12-11 17:22:22 +0000108 /vim_accounts (also vims for compatibility) O O
109 /<id> O O O
110 /wim_accounts O O
tierno2236d202018-05-16 19:05:16 +0200111 /<id> O O O
tierno0f98af52018-03-19 10:28:22 +0100112 /sdns O O
tierno2236d202018-05-16 19:05:16 +0200113 /<id> O O O
delacruzramofe598fe2019-10-23 18:25:11 +0200114 /k8sclusters O O
115 /<id> O O O
116 /k8srepos O O
117 /<id> O O
Felipe Vicensb66b0412020-05-06 10:11:00 +0200118 /osmrepos O O
119 /<id> O O
tiernoc94c3df2018-02-09 15:38:54 +0100120
garciadeblas9750c5a2018-10-15 16:20:35 +0200121 /nst/v1 O O
122 /netslice_templates_content O O
123 /<nstInfoId> O O O O
124 /netslice_templates O O
125 /<nstInfoId> O O O
126 /nst_content O O
127 /nst O
128 /artifacts[/<artifactPath>] O
129 /subscriptions X X
130 /<subscriptionId> X X
131
132 /nsilcm/v1
133 /netslice_instances_content O O
134 /<SliceInstanceId> O O
135 /netslice_instances O O
136 /<SliceInstanceId> O O
137 instantiate O
138 terminate O
139 action O
140 /nsi_lcm_op_occs O O
141 /<nsiLcmOpOccId> O O O
142 /subscriptions X X
143 /<subscriptionId> X X
144
tierno2236d202018-05-16 19:05:16 +0200145query string:
146 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
tierno36ec8602018-11-02 17:27:11 +0100147 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
148 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
149 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
150 attrName := string
tierno2236d202018-05-16 19:05:16 +0200151 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
152 item of the array, that is, pass if any item of the array pass the filter.
153 It allows both ne and neq for not equal
154 TODO: 4.3.3 Attribute selectors
155 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
tiernoc94c3df2018-02-09 15:38:54 +0100156 (none) … same as “exclude_default”
157 all_fields … all attributes.
tierno2236d202018-05-16 19:05:16 +0200158 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
159 conditionally mandatory, and that are not provided in <list>.
160 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
161 are not conditionally mandatory, and that are provided in <list>.
162 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
163 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
164 the particular resource
165 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
166 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
167 present specification for the particular resource, but that are not part of <list>
tierno65ca36d2019-02-12 19:27:52 +0100168 Additionally it admits some administrator values:
169 FORCE: To force operations skipping dependency checkings
170 ADMIN: To act as an administrator or a different project
171 PUBLIC: To get public descriptors or set a descriptor as public
172 SET_PROJECT: To make a descriptor available for other project
173
tiernoc94c3df2018-02-09 15:38:54 +0100174Header field name Reference Example Descriptions
175 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
176 This header field shall be present if the response is expected to have a non-empty message body.
177 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
178 This header field shall be present if the request has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200179 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
180 Details are specified in clause 4.5.3.
tiernoc94c3df2018-02-09 15:38:54 +0100181 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
182Header field name Reference Example Descriptions
183 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
184 This header field shall be present if the response has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200185 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
186 new resource has been created.
tiernoc94c3df2018-02-09 15:38:54 +0100187 This header field shall be present if the response status code is 201 or 3xx.
tierno2236d202018-05-16 19:05:16 +0200188 In the present document this header field is also used if the response status code is 202 and a new resource was
189 created.
190 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
191 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
192 token.
193 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
194 certain resources.
195 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
196 response, and the total length of the file.
tiernoc94c3df2018-02-09 15:38:54 +0100197 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
tiernoc94c3df2018-02-09 15:38:54 +0100198"""
199
tierno701018c2019-06-25 11:13:14 +0000200valid_query_string = ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
201# ^ Contains possible administrative query string words:
202# ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
203# (not owned by my session project).
204# PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
205# FORCE=True(by default)|False: Force edition/deletion operations
206# SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
207
208valid_url_methods = {
209 # contains allowed URL and methods, and the role_permission name
210 "admin": {
211 "v1": {
212 "tokens": {"METHODS": ("GET", "POST", "DELETE"),
213 "ROLE_PERMISSION": "tokens:",
214 "<ID>": {"METHODS": ("GET", "DELETE"),
215 "ROLE_PERMISSION": "tokens:id:"
216 }
217 },
218 "users": {"METHODS": ("GET", "POST"),
219 "ROLE_PERMISSION": "users:",
delacruzramo1459d602019-10-03 14:22:00 +0200220 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno701018c2019-06-25 11:13:14 +0000221 "ROLE_PERMISSION": "users:id:"
222 }
223 },
224 "projects": {"METHODS": ("GET", "POST"),
225 "ROLE_PERMISSION": "projects:",
delacruzramo1459d602019-10-03 14:22:00 +0200226 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno701018c2019-06-25 11:13:14 +0000227 "ROLE_PERMISSION": "projects:id:"}
228 },
229 "roles": {"METHODS": ("GET", "POST"),
230 "ROLE_PERMISSION": "roles:",
delacruzramo1459d602019-10-03 14:22:00 +0200231 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno701018c2019-06-25 11:13:14 +0000232 "ROLE_PERMISSION": "roles:id:"
233 }
234 },
235 "vims": {"METHODS": ("GET", "POST"),
236 "ROLE_PERMISSION": "vims:",
delacruzramo1459d602019-10-03 14:22:00 +0200237 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno701018c2019-06-25 11:13:14 +0000238 "ROLE_PERMISSION": "vims:id:"
239 }
240 },
241 "vim_accounts": {"METHODS": ("GET", "POST"),
242 "ROLE_PERMISSION": "vim_accounts:",
delacruzramo1459d602019-10-03 14:22:00 +0200243 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno701018c2019-06-25 11:13:14 +0000244 "ROLE_PERMISSION": "vim_accounts:id:"
245 }
246 },
247 "wim_accounts": {"METHODS": ("GET", "POST"),
248 "ROLE_PERMISSION": "wim_accounts:",
delacruzramo1459d602019-10-03 14:22:00 +0200249 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno701018c2019-06-25 11:13:14 +0000250 "ROLE_PERMISSION": "wim_accounts:id:"
251 }
252 },
253 "sdns": {"METHODS": ("GET", "POST"),
254 "ROLE_PERMISSION": "sdn_controllers:",
delacruzramo1459d602019-10-03 14:22:00 +0200255 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno701018c2019-06-25 11:13:14 +0000256 "ROLE_PERMISSION": "sdn_controllers:id:"
257 }
258 },
delacruzramofe598fe2019-10-23 18:25:11 +0200259 "k8sclusters": {"METHODS": ("GET", "POST"),
260 "ROLE_PERMISSION": "k8sclusters:",
delacruzramo549dda62019-11-14 09:57:35 +0100261 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
delacruzramofe598fe2019-10-23 18:25:11 +0200262 "ROLE_PERMISSION": "k8sclusters:id:"
263 }
264 },
265 "k8srepos": {"METHODS": ("GET", "POST"),
266 "ROLE_PERMISSION": "k8srepos:",
267 "<ID>": {"METHODS": ("GET", "DELETE"),
268 "ROLE_PERMISSION": "k8srepos:id:"
269 }
270 },
Felipe Vicensb66b0412020-05-06 10:11:00 +0200271 "osmrepos": {"METHODS": ("GET", "POST"),
272 "ROLE_PERMISSION": "osmrepos:",
273 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
274 "ROLE_PERMISSION": "osmrepos:id:"
275 }
276 },
tierno12eac3c2020-03-19 23:22:08 +0000277 "domains": {"METHODS": ("GET", ),
278 "ROLE_PERMISSION": "domains:",
279 },
tierno701018c2019-06-25 11:13:14 +0000280 }
281 },
282 "pdu": {
283 "v1": {
284 "pdu_descriptors": {"METHODS": ("GET", "POST"),
285 "ROLE_PERMISSION": "pduds:",
286 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
287 "ROLE_PERMISSION": "pduds:id:"
288 }
289 },
290 }
291 },
292 "nsd": {
293 "v1": {
294 "ns_descriptors_content": {"METHODS": ("GET", "POST"),
295 "ROLE_PERMISSION": "nsds:",
296 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
297 "ROLE_PERMISSION": "nsds:id:"
298 }
299 },
300 "ns_descriptors": {"METHODS": ("GET", "POST"),
301 "ROLE_PERMISSION": "nsds:",
302 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
303 "ROLE_PERMISSION": "nsds:id:",
304 "nsd_content": {"METHODS": ("GET", "PUT"),
305 "ROLE_PERMISSION": "nsds:id:content:",
306 },
307 "nsd": {"METHODS": ("GET",), # descriptor inside package
308 "ROLE_PERMISSION": "nsds:id:content:"
309 },
tierno74b53582020-06-18 10:52:37 +0000310 "artifacts": {"METHODS": ("GET",),
311 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
312 "*": None,
tierno701018c2019-06-25 11:13:14 +0000313 }
314 }
315 },
316 "pnf_descriptors": {"TODO": ("GET", "POST"),
317 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
318 "pnfd_content": {"TODO": ("GET", "PUT")}
319 }
320 },
321 "subscriptions": {"TODO": ("GET", "POST"),
322 "<ID>": {"TODO": ("GET", "DELETE")}
323 },
324 }
325 },
326 "vnfpkgm": {
327 "v1": {
328 "vnf_packages_content": {"METHODS": ("GET", "POST"),
329 "ROLE_PERMISSION": "vnfds:",
330 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
331 "ROLE_PERMISSION": "vnfds:id:"}
332 },
333 "vnf_packages": {"METHODS": ("GET", "POST"),
334 "ROLE_PERMISSION": "vnfds:",
335 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
336 "ROLE_PERMISSION": "vnfds:id:",
337 "package_content": {"METHODS": ("GET", "PUT"), # package
338 "ROLE_PERMISSION": "vnfds:id:",
339 "upload_from_uri": {"METHODS": (),
340 "TODO": ("POST", ),
341 "ROLE_PERMISSION": "vnfds:id:upload:"
342 }
343 },
344 "vnfd": {"METHODS": ("GET", ), # descriptor inside package
345 "ROLE_PERMISSION": "vnfds:id:content:"
346 },
tierno74b53582020-06-18 10:52:37 +0000347 "artifacts": {"METHODS": ("GET", ),
348 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
349 "*": None,
delacruzramo271d2002019-12-02 21:00:37 +0100350 },
351 "action": {"METHODS": ("POST", ),
352 "ROLE_PERMISSION": "vnfds:id:action:"
353 },
tierno701018c2019-06-25 11:13:14 +0000354 }
355 },
356 "subscriptions": {"TODO": ("GET", "POST"),
357 "<ID>": {"TODO": ("GET", "DELETE")}
358 },
delacruzramo271d2002019-12-02 21:00:37 +0100359 "vnfpkg_op_occs": {"METHODS": ("GET", ),
360 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
361 "<ID>": {"METHODS": ("GET", ),
362 "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"
363 }
364 },
tierno701018c2019-06-25 11:13:14 +0000365 }
366 },
367 "nslcm": {
368 "v1": {
369 "ns_instances_content": {"METHODS": ("GET", "POST"),
370 "ROLE_PERMISSION": "ns_instances:",
371 "<ID>": {"METHODS": ("GET", "DELETE"),
372 "ROLE_PERMISSION": "ns_instances:id:"
373 }
374 },
375 "ns_instances": {"METHODS": ("GET", "POST"),
376 "ROLE_PERMISSION": "ns_instances:",
377 "<ID>": {"METHODS": ("GET", "DELETE"),
378 "ROLE_PERMISSION": "ns_instances:id:",
379 "scale": {"METHODS": ("POST",),
380 "ROLE_PERMISSION": "ns_instances:id:scale:"
381 },
382 "terminate": {"METHODS": ("POST",),
383 "ROLE_PERMISSION": "ns_instances:id:terminate:"
384 },
385 "instantiate": {"METHODS": ("POST",),
386 "ROLE_PERMISSION": "ns_instances:id:instantiate:"
387 },
388 "action": {"METHODS": ("POST",),
389 "ROLE_PERMISSION": "ns_instances:id:action:"
390 },
391 }
392 },
393 "ns_lcm_op_occs": {"METHODS": ("GET",),
394 "ROLE_PERMISSION": "ns_instances:opps:",
395 "<ID>": {"METHODS": ("GET",),
396 "ROLE_PERMISSION": "ns_instances:opps:id:"
397 },
398 },
399 "vnfrs": {"METHODS": ("GET",),
400 "ROLE_PERMISSION": "vnf_instances:",
401 "<ID>": {"METHODS": ("GET",),
402 "ROLE_PERMISSION": "vnf_instances:id:"
403 }
404 },
405 "vnf_instances": {"METHODS": ("GET",),
406 "ROLE_PERMISSION": "vnf_instances:",
407 "<ID>": {"METHODS": ("GET",),
408 "ROLE_PERMISSION": "vnf_instances:id:"
409 }
410 },
preethika.p329b8182020-04-22 12:25:39 +0530411 "subscriptions": {"METHODS": ("GET", "POST"),
412 "ROLE_PERMISSION": "ns_subscriptions:",
413 "<ID>": {"METHODS": ("GET", "DELETE"),
414 "ROLE_PERMISSION": "ns_subscriptions:id:"
415 }
416 },
tierno701018c2019-06-25 11:13:14 +0000417 }
418 },
419 "nst": {
420 "v1": {
421 "netslice_templates_content": {"METHODS": ("GET", "POST"),
422 "ROLE_PERMISSION": "slice_templates:",
423 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
424 "ROLE_PERMISSION": "slice_templates:id:", }
425 },
426 "netslice_templates": {"METHODS": ("GET", "POST"),
427 "ROLE_PERMISSION": "slice_templates:",
428 "<ID>": {"METHODS": ("GET", "DELETE"),
429 "TODO": ("PATCH",),
430 "ROLE_PERMISSION": "slice_templates:id:",
431 "nst_content": {"METHODS": ("GET", "PUT"),
432 "ROLE_PERMISSION": "slice_templates:id:content:"
433 },
434 "nst": {"METHODS": ("GET",), # descriptor inside package
435 "ROLE_PERMISSION": "slice_templates:id:content:"
436 },
tierno74b53582020-06-18 10:52:37 +0000437 "artifacts": {"METHODS": ("GET",),
438 "ROLE_PERMISSION": "slice_templates:id:content:",
439 "*": None
tierno701018c2019-06-25 11:13:14 +0000440 }
441 }
442 },
443 "subscriptions": {"TODO": ("GET", "POST"),
444 "<ID>": {"TODO": ("GET", "DELETE")}
445 },
446 }
447 },
448 "nsilcm": {
449 "v1": {
450 "netslice_instances_content": {"METHODS": ("GET", "POST"),
451 "ROLE_PERMISSION": "slice_instances:",
452 "<ID>": {"METHODS": ("GET", "DELETE"),
453 "ROLE_PERMISSION": "slice_instances:id:"
454 }
455 },
456 "netslice_instances": {"METHODS": ("GET", "POST"),
457 "ROLE_PERMISSION": "slice_instances:",
458 "<ID>": {"METHODS": ("GET", "DELETE"),
459 "ROLE_PERMISSION": "slice_instances:id:",
460 "terminate": {"METHODS": ("POST",),
461 "ROLE_PERMISSION": "slice_instances:id:terminate:"
462 },
463 "instantiate": {"METHODS": ("POST",),
464 "ROLE_PERMISSION": "slice_instances:id:instantiate:"
465 },
466 "action": {"METHODS": ("POST",),
467 "ROLE_PERMISSION": "slice_instances:id:action:"
468 },
469 }
470 },
471 "nsi_lcm_op_occs": {"METHODS": ("GET",),
472 "ROLE_PERMISSION": "slice_instances:opps:",
473 "<ID>": {"METHODS": ("GET",),
474 "ROLE_PERMISSION": "slice_instances:opps:id:",
475 },
476 },
477 }
478 },
479 "nspm": {
480 "v1": {
481 "pm_jobs": {
482 "<ID>": {
483 "reports": {
484 "<ID>": {"METHODS": ("GET",),
485 "ROLE_PERMISSION": "reports:id:",
486 }
487 }
488 },
489 },
490 },
491 },
492}
493
tiernoc94c3df2018-02-09 15:38:54 +0100494
495class NbiException(Exception):
496
497 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
498 Exception.__init__(self, message)
499 self.http_code = http_code
500
501
502class Server(object):
503 instance = 0
504 # to decode bytes to str
505 reader = getreader("utf-8")
506
507 def __init__(self):
508 self.instance += 1
tierno701018c2019-06-25 11:13:14 +0000509 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
delacruzramoad682a52019-12-10 16:26:34 +0100510 self.engine = Engine(self.authenticator)
tiernoc94c3df2018-02-09 15:38:54 +0100511
tiernoc94c3df2018-02-09 15:38:54 +0100512 def _format_in(self, kwargs):
513 try:
514 indata = None
515 if cherrypy.request.body.length:
516 error_text = "Invalid input format "
517
518 if "Content-Type" in cherrypy.request.headers:
519 if "application/json" in cherrypy.request.headers["Content-Type"]:
520 error_text = "Invalid json format "
521 indata = json.load(self.reader(cherrypy.request.body))
gcalvinode4adfe2018-10-30 11:46:09 +0100522 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100523 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
524 error_text = "Invalid yaml format "
delacruzramob19cadc2019-10-08 10:18:02 +0200525 indata = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
gcalvinode4adfe2018-10-30 11:46:09 +0100526 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100527 elif "application/binary" in cherrypy.request.headers["Content-Type"] or \
528 "application/gzip" in cherrypy.request.headers["Content-Type"] or \
tiernof27c79b2018-03-12 17:08:42 +0100529 "application/zip" in cherrypy.request.headers["Content-Type"] or \
530 "text/plain" in cherrypy.request.headers["Content-Type"]:
531 indata = cherrypy.request.body # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100532 elif "multipart/form-data" in cherrypy.request.headers["Content-Type"]:
533 if "descriptor_file" in kwargs:
534 filecontent = kwargs.pop("descriptor_file")
535 if not filecontent.file:
536 raise NbiException("empty file or content", HTTPStatus.BAD_REQUEST)
tiernof27c79b2018-03-12 17:08:42 +0100537 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100538 if filecontent.content_type.value:
539 cherrypy.request.headers["Content-Type"] = filecontent.content_type.value
540 else:
541 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
542 # "Only 'Content-Type' of type 'application/json' or
543 # 'application/yaml' for input format are available")
544 error_text = "Invalid yaml format "
delacruzramob19cadc2019-10-08 10:18:02 +0200545 indata = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
gcalvinode4adfe2018-10-30 11:46:09 +0100546 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100547 else:
548 error_text = "Invalid yaml format "
delacruzramob19cadc2019-10-08 10:18:02 +0200549 indata = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
gcalvinode4adfe2018-10-30 11:46:09 +0100550 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100551 if not indata:
552 indata = {}
553
tiernoc94c3df2018-02-09 15:38:54 +0100554 format_yaml = False
555 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
556 format_yaml = True
557
558 for k, v in kwargs.items():
559 if isinstance(v, str):
560 if v == "":
561 kwargs[k] = None
562 elif format_yaml:
563 try:
delacruzramob19cadc2019-10-08 10:18:02 +0200564 kwargs[k] = yaml.load(v, Loader=yaml.SafeLoader)
tiernoe1281182018-05-22 12:24:36 +0200565 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100566 pass
567 elif k.endswith(".gt") or k.endswith(".lt") or k.endswith(".gte") or k.endswith(".lte"):
568 try:
569 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200570 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100571 try:
572 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200573 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100574 pass
575 elif v.find(",") > 0:
576 kwargs[k] = v.split(",")
577 elif isinstance(v, (list, tuple)):
578 for index in range(0, len(v)):
579 if v[index] == "":
580 v[index] = None
581 elif format_yaml:
582 try:
delacruzramob19cadc2019-10-08 10:18:02 +0200583 v[index] = yaml.load(v[index], Loader=yaml.SafeLoader)
tiernoe1281182018-05-22 12:24:36 +0200584 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100585 pass
586
tiernof27c79b2018-03-12 17:08:42 +0100587 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100588 except (ValueError, yaml.YAMLError) as exc:
589 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
590 except KeyError as exc:
591 raise NbiException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
tiernob92094f2018-05-11 13:44:22 +0200592 except Exception as exc:
593 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100594
595 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000596 def _format_out(data, token_info=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100597 """
598 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100599 :param data: response to be sent. Can be a dict, text or file
tierno701018c2019-06-25 11:13:14 +0000600 :param token_info: Contains among other username and project
tierno5792d7d2019-08-30 15:37:12 +0000601 :param _format: The format to be set as Content-Type if data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100602 :return: None
603 """
tierno0f98af52018-03-19 10:28:22 +0100604 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100605 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100606 if accept and "text/html" in accept:
tierno701018c2019-06-25 11:13:14 +0000607 return html.format(data, cherrypy.request, cherrypy.response, token_info)
tierno09c073e2018-04-26 13:36:48 +0200608 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100609 return
610 elif hasattr(data, "read"): # file object
611 if _format:
612 cherrypy.response.headers["Content-Type"] = _format
613 elif "b" in data.mode: # binariy asssumig zip
614 cherrypy.response.headers["Content-Type"] = 'application/zip'
615 else:
616 cherrypy.response.headers["Content-Type"] = 'text/plain'
617 # TODO check that cherrypy close file. If not implement pending things to close per thread next
618 return data
tierno0f98af52018-03-19 10:28:22 +0100619 if accept:
tiernoc94c3df2018-02-09 15:38:54 +0100620 if "application/json" in accept:
621 cherrypy.response.headers["Content-Type"] = 'application/json; charset=utf-8'
622 a = json.dumps(data, indent=4) + "\n"
623 return a.encode("utf8")
624 elif "text/html" in accept:
tierno701018c2019-06-25 11:13:14 +0000625 return html.format(data, cherrypy.request, cherrypy.response, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100626
tiernof27c79b2018-03-12 17:08:42 +0100627 elif "application/yaml" in accept or "*/*" in accept or "text/plain" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100628 pass
tiernof717cbe2018-12-03 16:35:42 +0000629 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
630 elif cherrypy.response.status >= 400:
tiernoc94c3df2018-02-09 15:38:54 +0100631 raise cherrypy.HTTPError(HTTPStatus.NOT_ACCEPTABLE.value,
632 "Only 'Accept' of type 'application/json' or 'application/yaml' "
633 "for output format are available")
634 cherrypy.response.headers["Content-Type"] = 'application/yaml'
635 return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False,
636 encoding='utf-8', allow_unicode=True) # , canonical=True, default_style='"'
637
638 @cherrypy.expose
639 def index(self, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000640 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100641 try:
642 if cherrypy.request.method == "GET":
tierno701018c2019-06-25 11:13:14 +0000643 token_info = self.authenticator.authorize()
delacruzramo01b15d32019-07-02 14:37:47 +0200644 outdata = token_info # Home page
tiernoc94c3df2018-02-09 15:38:54 +0100645 else:
646 raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value,
tierno2236d202018-05-16 19:05:16 +0200647 "Method {} not allowed for tokens".format(cherrypy.request.method))
tiernoc94c3df2018-02-09 15:38:54 +0100648
tierno701018c2019-06-25 11:13:14 +0000649 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100650
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100651 except (EngineException, AuthException) as e:
tierno701018c2019-06-25 11:13:14 +0000652 # cherrypy.log("index Exception {}".format(e))
tiernoc94c3df2018-02-09 15:38:54 +0100653 cherrypy.response.status = e.http_code.value
tierno701018c2019-06-25 11:13:14 +0000654 return self._format_out("Welcome to OSM!", token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100655
656 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200657 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200658 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200659 try:
660 if cherrypy.request.method != "GET":
661 raise NbiException("Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED)
662 elif args or kwargs:
663 raise NbiException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED)
tierno9c630112019-08-29 14:21:41 +0000664 # TODO include version of other modules, pick up from some kafka admin message
tierno5792d7d2019-08-30 15:37:12 +0000665 osm_nbi_version = {"version": nbi_version, "date": nbi_version_date}
666 return self._format_out(osm_nbi_version)
tierno55945e72018-04-06 16:40:27 +0200667 except NbiException as e:
668 cherrypy.response.status = e.http_code.value
669 problem_details = {
670 "code": e.http_code.name,
671 "status": e.http_code.value,
672 "detail": str(e),
673 }
674 return self._format_out(problem_details, None)
675
tierno12eac3c2020-03-19 23:22:08 +0000676 def domain(self):
677 try:
678 domains = {
679 "user_domain_name": cherrypy.tree.apps['/osm'].config["authentication"].get("user_domain_name"),
680 "project_domain_name": cherrypy.tree.apps['/osm'].config["authentication"].get("project_domain_name")}
681 return self._format_out(domains)
682 except NbiException as e:
683 cherrypy.response.status = e.http_code.value
684 problem_details = {
685 "code": e.http_code.name,
686 "status": e.http_code.value,
687 "detail": str(e),
688 }
689 return self._format_out(problem_details, None)
690
tiernoa5035702019-07-29 08:54:42 +0000691 @staticmethod
692 def _format_login(token_info):
693 """
694 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
695 log this information
696 :param token_info: Dictionary with token content
697 :return: None
698 """
699 cherrypy.request.login = token_info.get("username", "-")
700 if token_info.get("project_name"):
701 cherrypy.request.login += "/" + token_info["project_name"]
702 if token_info.get("id"):
703 cherrypy.request.login += ";session=" + token_info["id"][0:12]
704
tierno55945e72018-04-06 16:40:27 +0200705 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100706 def token(self, method, token_id=None, kwargs=None):
tierno701018c2019-06-25 11:13:14 +0000707 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100708 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +0100709 indata = self._format_in(kwargs)
710 if not isinstance(indata, dict):
711 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST)
tiernoa5035702019-07-29 08:54:42 +0000712
713 if method == "GET":
714 token_info = self.authenticator.authorize()
715 # for logging
716 self._format_login(token_info)
717 if token_id:
718 outdata = self.authenticator.get_token(token_info, token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100719 else:
tiernoa5035702019-07-29 08:54:42 +0000720 outdata = self.authenticator.get_token_list(token_info)
721 elif method == "POST":
722 try:
723 token_info = self.authenticator.authorize()
724 except Exception:
725 token_info = None
726 if kwargs:
727 indata.update(kwargs)
728 # This is needed to log the user when authentication fails
729 cherrypy.request.login = "{}".format(indata.get("username", "-"))
730 outdata = token_info = self.authenticator.new_token(token_info, indata, cherrypy.request.remote)
731 cherrypy.session['Authorization'] = outdata["_id"]
732 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
733 # for logging
734 self._format_login(token_info)
735
736 # cherrypy.response.cookie["Authorization"] = outdata["id"]
737 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
738 elif method == "DELETE":
739 if not token_id and "id" in kwargs:
740 token_id = kwargs["id"]
741 elif not token_id:
742 token_info = self.authenticator.authorize()
743 # for logging
744 self._format_login(token_info)
745 token_id = token_info["_id"]
746 outdata = self.authenticator.del_token(token_id)
747 token_info = None
748 cherrypy.session['Authorization'] = "logout"
749 # cherrypy.response.cookie["Authorization"] = token_id
750 # cherrypy.response.cookie["Authorization"]['expires'] = 0
751 else:
752 raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
753 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100754
755 @cherrypy.expose
756 def test(self, *args, **kwargs):
tierno4836bac2020-01-15 14:41:48 +0000757 if not cherrypy.config.get("server.enable_test") or (isinstance(cherrypy.config["server.enable_test"], str) and
758 cherrypy.config["server.enable_test"].lower() == "false"):
759 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value
760 return "test URL is disabled"
tiernoc94c3df2018-02-09 15:38:54 +0100761 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +0100762 if args and args[0] == "help":
tiernof717cbe2018-12-03 16:35:42 +0000763 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
tiernoe1281182018-05-22 12:24:36 +0200764 "sleep/<time>\nmessage/topic\n</pre></html>"
tiernof27c79b2018-03-12 17:08:42 +0100765
766 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +0100767 try:
768 # self.engine.load_dbase(cherrypy.request.app.config)
769 self.engine.create_admin()
770 return "Done. User 'admin', password 'admin' created"
771 except Exception:
772 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
773 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +0100774 elif args and args[0] == "file":
775 return cherrypy.lib.static.serve_file(cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1],
776 "text/plain", "attachment")
777 elif args and args[0] == "file2":
778 f_path = cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1]
779 f = open(f_path, "r")
780 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100781 return f
tierno55945e72018-04-06 16:40:27 +0200782
tiernof27c79b2018-03-12 17:08:42 +0100783 elif len(args) == 2 and args[0] == "db-clear":
tiernof717cbe2018-12-03 16:35:42 +0000784 deleted_info = self.engine.db.del_list(args[1], kwargs)
785 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
786 elif len(args) and args[0] == "fs-clear":
787 if len(args) >= 2:
788 folders = (args[1],)
789 else:
790 folders = self.engine.fs.dir_ls(".")
791 for folder in folders:
792 self.engine.fs.file_delete(folder)
793 return ",".join(folders) + " folders deleted\n"
tiernoc94c3df2018-02-09 15:38:54 +0100794 elif args and args[0] == "login":
795 if not cherrypy.request.headers.get("Authorization"):
796 cherrypy.response.headers["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
797 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
798 elif args and args[0] == "login2":
799 if not cherrypy.request.headers.get("Authorization"):
800 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
801 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
802 elif args and args[0] == "sleep":
803 sleep_time = 5
804 try:
805 sleep_time = int(args[1])
806 except Exception:
807 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
808 return self._format_out("Database already initialized")
809 thread_info = cherrypy.thread_data
810 print(thread_info)
811 time.sleep(sleep_time)
812 # thread_info
813 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +0200814 main_topic = args[1]
815 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +0100816 try:
tierno55945e72018-04-06 16:40:27 +0200817 if cherrypy.request.method == 'POST':
delacruzramob19cadc2019-10-08 10:18:02 +0200818 to_send = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
tierno55945e72018-04-06 16:40:27 +0200819 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +0200820 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +0200821 return_text += " {}: {}\n".format(k, v)
822 elif cherrypy.request.method == 'GET':
823 for k, v in kwargs.items():
tiernof1509b22020-05-12 14:32:37 +0000824 v_dict = yaml.load(v, Loader=yaml.SafeLoader)
825 self.engine.msg.write(main_topic, k, v_dict)
826 return_text += " {}: {}\n".format(k, v_dict)
tiernoc94c3df2018-02-09 15:38:54 +0100827 except Exception as e:
tierno55945e72018-04-06 16:40:27 +0200828 return_text += "Error: " + str(e)
829 return_text += "</pre></html>\n"
830 return return_text
tiernoc94c3df2018-02-09 15:38:54 +0100831
832 return_text = (
833 "<html><pre>\nheaders:\n args: {}\n".format(args) +
834 " kwargs: {}\n".format(kwargs) +
835 " headers: {}\n".format(cherrypy.request.headers) +
836 " path_info: {}\n".format(cherrypy.request.path_info) +
837 " query_string: {}\n".format(cherrypy.request.query_string) +
838 " session: {}\n".format(cherrypy.session) +
839 " cookie: {}\n".format(cherrypy.request.cookie) +
840 " method: {}\n".format(cherrypy.request.method) +
tiernof717cbe2018-12-03 16:35:42 +0000841 " session: {}\n".format(cherrypy.session.get('fieldname')) +
tiernoc94c3df2018-02-09 15:38:54 +0100842 " body:\n")
843 return_text += " length: {}\n".format(cherrypy.request.body.length)
844 if cherrypy.request.body.length:
845 return_text += " content: {}\n".format(
846 str(cherrypy.request.body.read(int(cherrypy.request.headers.get('Content-Length', 0)))))
847 if thread_info:
848 return_text += "thread: {}\n".format(thread_info)
849 return_text += "</pre></html>"
850 return return_text
851
tierno701018c2019-06-25 11:13:14 +0000852 @staticmethod
853 def _check_valid_url_method(method, *args):
tiernof27c79b2018-03-12 17:08:42 +0100854 if len(args) < 3:
tiernob24258a2018-10-04 18:39:49 +0200855 raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus.METHOD_NOT_ALLOWED)
tiernof27c79b2018-03-12 17:08:42 +0100856
tierno701018c2019-06-25 11:13:14 +0000857 reference = valid_url_methods
tiernof27c79b2018-03-12 17:08:42 +0100858 for arg in args:
859 if arg is None:
860 break
861 if not isinstance(reference, dict):
862 raise NbiException("URL contains unexpected extra items '{}'".format(arg),
863 HTTPStatus.METHOD_NOT_ALLOWED)
864
865 if arg in reference:
866 reference = reference[arg]
867 elif "<ID>" in reference:
868 reference = reference["<ID>"]
869 elif "*" in reference:
tierno74b53582020-06-18 10:52:37 +0000870 # if there is content
871 if reference["*"]:
872 reference = reference["*"]
tiernof27c79b2018-03-12 17:08:42 +0100873 break
874 else:
875 raise NbiException("Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED)
876 if "TODO" in reference and method in reference["TODO"]:
877 raise NbiException("Method {} not supported yet for this URL".format(method), HTTPStatus.NOT_IMPLEMENTED)
tierno2236d202018-05-16 19:05:16 +0200878 elif "METHODS" in reference and method not in reference["METHODS"]:
tiernof27c79b2018-03-12 17:08:42 +0100879 raise NbiException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tierno701018c2019-06-25 11:13:14 +0000880 return reference["ROLE_PERMISSION"] + method.lower()
tiernof27c79b2018-03-12 17:08:42 +0100881
882 @staticmethod
tiernob24258a2018-10-04 18:39:49 +0200883 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +0100884 """
885 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +0200886 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +0100887 :param version:
tiernob24258a2018-10-04 18:39:49 +0200888 :param topic:
tiernof27c79b2018-03-12 17:08:42 +0100889 :param id:
890 :return: None
891 """
892 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
tiernob24258a2018-10-04 18:39:49 +0200893 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(main_topic, version, topic, id)
tiernof27c79b2018-03-12 17:08:42 +0100894 return
895
tierno65ca36d2019-02-12 19:27:52 +0100896 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000897 def _extract_query_string_operations(kwargs, method):
898 """
899
900 :param kwargs:
901 :return:
902 """
903 query_string_operations = []
904 if kwargs:
905 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
906 if qs in kwargs and kwargs[qs].lower() != "false":
907 query_string_operations.append(qs.lower() + ":" + method.lower())
908 return query_string_operations
909
910 @staticmethod
911 def _manage_admin_query(token_info, kwargs, method, _id):
tierno65ca36d2019-02-12 19:27:52 +0100912 """
913 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
914 Check that users has rights to use them and returs the admin_query
tierno701018c2019-06-25 11:13:14 +0000915 :param token_info: token_info rights obtained by token
tierno65ca36d2019-02-12 19:27:52 +0100916 :param kwargs: query string input.
917 :param method: http method: GET, POSST, PUT, ...
918 :param _id:
919 :return: admin_query dictionary with keys:
920 public: True, False or None
921 force: True or False
922 project_id: tuple with projects used for accessing an element
923 set_project: tuple with projects that a created element will belong to
924 method: show, list, delete, write
925 """
tierno701018c2019-06-25 11:13:14 +0000926 admin_query = {"force": False, "project_id": (token_info["project_id"], ), "username": token_info["username"],
delacruzramo029405d2019-09-26 10:52:56 +0200927 "admin": token_info["admin"], "public": None,
928 "allow_show_user_project_role": token_info["allow_show_user_project_role"]}
tierno65ca36d2019-02-12 19:27:52 +0100929 if kwargs:
930 # FORCE
931 if "FORCE" in kwargs:
932 if kwargs["FORCE"].lower() != "false": # if None or True set force to True
933 admin_query["force"] = True
934 del kwargs["FORCE"]
935 # PUBLIC
936 if "PUBLIC" in kwargs:
937 if kwargs["PUBLIC"].lower() != "false": # if None or True set public to True
938 admin_query["public"] = True
939 else:
940 admin_query["public"] = False
941 del kwargs["PUBLIC"]
942 # ADMIN
943 if "ADMIN" in kwargs:
944 behave_as = kwargs.pop("ADMIN")
945 if behave_as.lower() != "false":
tierno701018c2019-06-25 11:13:14 +0000946 if not token_info["admin"]:
tierno65ca36d2019-02-12 19:27:52 +0100947 raise NbiException("Only admin projects can use 'ADMIN' query string", HTTPStatus.UNAUTHORIZED)
948 if not behave_as or behave_as.lower() == "true": # convert True, None to empty list
949 admin_query["project_id"] = ()
950 elif isinstance(behave_as, (list, tuple)):
951 admin_query["project_id"] = behave_as
952 else: # isinstance(behave_as, str)
953 admin_query["project_id"] = (behave_as, )
954 if "SET_PROJECT" in kwargs:
955 set_project = kwargs.pop("SET_PROJECT")
956 if not set_project:
957 admin_query["set_project"] = list(admin_query["project_id"])
958 else:
959 if isinstance(set_project, str):
960 set_project = (set_project, )
961 if admin_query["project_id"]:
962 for p in set_project:
963 if p not in admin_query["project_id"]:
964 raise NbiException("Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
965 "'ADMIN='{p}'".format(p=p), HTTPStatus.UNAUTHORIZED)
966 admin_query["set_project"] = set_project
967
968 # PROJECT_READ
969 # if "PROJECT_READ" in kwargs:
970 # admin_query["project"] = kwargs.pop("project")
tierno701018c2019-06-25 11:13:14 +0000971 # if admin_query["project"] == token_info["project_id"]:
tierno65ca36d2019-02-12 19:27:52 +0100972 if method == "GET":
973 if _id:
974 admin_query["method"] = "show"
975 else:
976 admin_query["method"] = "list"
977 elif method == "DELETE":
978 admin_query["method"] = "delete"
979 else:
980 admin_query["method"] = "write"
981 return admin_query
982
tiernoc94c3df2018-02-09 15:38:54 +0100983 @cherrypy.expose
tiernob24258a2018-10-04 18:39:49 +0200984 def default(self, main_topic=None, version=None, topic=None, _id=None, item=None, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000985 token_info = None
tiernof27c79b2018-03-12 17:08:42 +0100986 outdata = None
987 _format = None
tierno0f98af52018-03-19 10:28:22 +0100988 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +0200989 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +0200990 rollback = []
tierno701018c2019-06-25 11:13:14 +0000991 engine_session = None
tiernoc94c3df2018-02-09 15:38:54 +0100992 try:
tiernob24258a2018-10-04 18:39:49 +0200993 if not main_topic or not version or not topic:
994 raise NbiException("URL must contain at least 'main_topic/version/topic'",
995 HTTPStatus.METHOD_NOT_ALLOWED)
vijay.r35ef2f72019-04-30 17:55:49 +0530996 if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm", "nspm"):
tiernob24258a2018-10-04 18:39:49 +0200997 raise NbiException("URL main_topic '{}' not supported".format(main_topic),
998 HTTPStatus.METHOD_NOT_ALLOWED)
tiernoc94c3df2018-02-09 15:38:54 +0100999 if version != 'v1':
1000 raise NbiException("URL version '{}' not supported".format(version), HTTPStatus.METHOD_NOT_ALLOWED)
1001
tiernof27c79b2018-03-12 17:08:42 +01001002 if kwargs and "METHOD" in kwargs and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
1003 method = kwargs.pop("METHOD")
1004 else:
1005 method = cherrypy.request.method
tierno65ca36d2019-02-12 19:27:52 +01001006
tierno701018c2019-06-25 11:13:14 +00001007 role_permission = self._check_valid_url_method(method, main_topic, version, topic, _id, item, *args)
1008 query_string_operations = self._extract_query_string_operations(kwargs, method)
tiernob24258a2018-10-04 18:39:49 +02001009 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +01001010 return self.token(method, _id, kwargs)
delacruzramo029405d2019-09-26 10:52:56 +02001011 token_info = self.authenticator.authorize(role_permission, query_string_operations, _id)
tierno12eac3c2020-03-19 23:22:08 +00001012 if main_topic == "admin" and topic == "domains":
1013 return self.domain()
tierno701018c2019-06-25 11:13:14 +00001014 engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
tiernof27c79b2018-03-12 17:08:42 +01001015 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +02001016 engine_topic = topic
preethika.p329b8182020-04-22 12:25:39 +05301017
vijay.r35ef2f72019-04-30 17:55:49 +05301018 if item and topic != "pm_jobs":
tiernob24258a2018-10-04 18:39:49 +02001019 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +01001020
tiernob24258a2018-10-04 18:39:49 +02001021 if main_topic == "nsd":
1022 engine_topic = "nsds"
1023 elif main_topic == "vnfpkgm":
1024 engine_topic = "vnfds"
delacruzramo271d2002019-12-02 21:00:37 +01001025 if topic == "vnfpkg_op_occs":
1026 engine_topic = "vnfpkgops"
1027 if topic == "vnf_packages" and item == "action":
1028 engine_topic = "vnfpkgops"
tiernob24258a2018-10-04 18:39:49 +02001029 elif main_topic == "nslcm":
1030 engine_topic = "nsrs"
1031 if topic == "ns_lcm_op_occs":
1032 engine_topic = "nslcmops"
1033 if topic == "vnfrs" or topic == "vnf_instances":
1034 engine_topic = "vnfrs"
garciadeblas9750c5a2018-10-15 16:20:35 +02001035 elif main_topic == "nst":
1036 engine_topic = "nsts"
1037 elif main_topic == "nsilcm":
1038 engine_topic = "nsis"
1039 if topic == "nsi_lcm_op_occs":
delacruzramoc061f562019-04-05 11:00:02 +02001040 engine_topic = "nsilcmops"
tiernob24258a2018-10-04 18:39:49 +02001041 elif main_topic == "pdu":
1042 engine_topic = "pdus"
tierno65ca36d2019-02-12 19:27:52 +01001043 if engine_topic == "vims": # TODO this is for backward compatibility, it will be removed in the future
tiernob24258a2018-10-04 18:39:49 +02001044 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +01001045
preethika.p329b8182020-04-22 12:25:39 +05301046 if topic == "subscriptions":
1047 engine_topic = main_topic + "_" + topic
1048
tiernoc94c3df2018-02-09 15:38:54 +01001049 if method == "GET":
Felipe Vicens07f31722018-10-29 15:16:44 +01001050 if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
garciadeblas9750c5a2018-10-15 16:20:35 +02001051 if item in ("vnfd", "nsd", "nst"):
tiernof27c79b2018-03-12 17:08:42 +01001052 path = "$DESCRIPTOR"
1053 elif args:
1054 path = args
tiernob24258a2018-10-04 18:39:49 +02001055 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +01001056 path = ()
1057 else:
1058 path = None
tierno701018c2019-06-25 11:13:14 +00001059 file, _format = self.engine.get_file(engine_session, engine_topic, _id, path,
tierno2236d202018-05-16 19:05:16 +02001060 cherrypy.request.headers.get("Accept"))
tiernof27c79b2018-03-12 17:08:42 +01001061 outdata = file
1062 elif not _id:
tierno701018c2019-06-25 11:13:14 +00001063 outdata = self.engine.get_item_list(engine_session, engine_topic, kwargs)
tiernoc94c3df2018-02-09 15:38:54 +01001064 else:
vijay.r35ef2f72019-04-30 17:55:49 +05301065 if item == "reports":
1066 # TODO check that project_id (_id in this context) has permissions
1067 _id = args[0]
tierno701018c2019-06-25 11:13:14 +00001068 outdata = self.engine.get_item(engine_session, engine_topic, _id)
delacruzramo271d2002019-12-02 21:00:37 +01001069
tiernof27c79b2018-03-12 17:08:42 +01001070 elif method == "POST":
tiernobdebce92019-07-01 15:36:49 +00001071 cherrypy.response.status = HTTPStatus.CREATED.value
garciadeblas9750c5a2018-10-15 16:20:35 +02001072 if topic in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"):
tiernof27c79b2018-03-12 17:08:42 +01001073 _id = cherrypy.request.headers.get("Transaction-Id")
1074 if not _id:
tiernobdebce92019-07-01 15:36:49 +00001075 _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, {}, None,
1076 cherrypy.request.headers)
tierno701018c2019-06-25 11:13:14 +00001077 completed = self.engine.upload_content(engine_session, engine_topic, _id, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +01001078 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +01001079 if completed:
tiernob24258a2018-10-04 18:39:49 +02001080 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001081 else:
1082 cherrypy.response.headers["Transaction-Id"] = _id
1083 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +02001084 elif topic == "ns_instances_content":
1085 # creates NSR
tiernobdebce92019-07-01 15:36:49 +00001086 _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs)
tiernob24258a2018-10-04 18:39:49 +02001087 # creates nslcmop
1088 indata["lcmOperationType"] = "instantiate"
1089 indata["nsInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001090 nslcmop_id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", indata, None)
tiernob24258a2018-10-04 18:39:49 +02001091 self._set_location_header(main_topic, version, topic, _id)
kuuse078f55e2019-05-16 19:24:21 +02001092 outdata = {"id": _id, "nslcmop_id": nslcmop_id}
tiernob24258a2018-10-04 18:39:49 +02001093 elif topic == "ns_instances" and item:
1094 indata["lcmOperationType"] = item
1095 indata["nsInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001096 _id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", indata, kwargs)
tiernob24258a2018-10-04 18:39:49 +02001097 self._set_location_header(main_topic, version, "ns_lcm_op_occs", _id)
tierno65acb4d2018-04-06 16:42:40 +02001098 outdata = {"id": _id}
1099 cherrypy.response.status = HTTPStatus.ACCEPTED.value
garciadeblas9750c5a2018-10-15 16:20:35 +02001100 elif topic == "netslice_instances_content":
Felipe Vicens07f31722018-10-29 15:16:44 +01001101 # creates NetSlice_Instance_record (NSIR)
tiernobdebce92019-07-01 15:36:49 +00001102 _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs)
Felipe Vicens07f31722018-10-29 15:16:44 +01001103 self._set_location_header(main_topic, version, topic, _id)
garciadeblas9750c5a2018-10-15 16:20:35 +02001104 indata["lcmOperationType"] = "instantiate"
Felipe Vicens126af572019-06-05 19:13:04 +02001105 indata["netsliceInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001106 nsilcmop_id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", indata, kwargs)
kuuse078f55e2019-05-16 19:24:21 +02001107 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
garciadeblas9750c5a2018-10-15 16:20:35 +02001108 elif topic == "netslice_instances" and item:
1109 indata["lcmOperationType"] = item
Felipe Vicens126af572019-06-05 19:13:04 +02001110 indata["netsliceInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001111 _id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", indata, kwargs)
garciadeblas9750c5a2018-10-15 16:20:35 +02001112 self._set_location_header(main_topic, version, "nsi_lcm_op_occs", _id)
1113 outdata = {"id": _id}
1114 cherrypy.response.status = HTTPStatus.ACCEPTED.value
delacruzramo271d2002019-12-02 21:00:37 +01001115 elif topic == "vnf_packages" and item == "action":
1116 indata["lcmOperationType"] = item
1117 indata["vnfPkgId"] = _id
1118 _id, _ = self.engine.new_item(rollback, engine_session, "vnfpkgops", indata, kwargs)
1119 self._set_location_header(main_topic, version, "vnfpkg_op_occs", _id)
1120 outdata = {"id": _id}
1121 cherrypy.response.status = HTTPStatus.ACCEPTED.value
preethika.p329b8182020-04-22 12:25:39 +05301122 elif topic == "subscriptions":
1123 _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs)
1124 self._set_location_header(main_topic, version, topic, _id)
1125 link = {}
1126 link["self"] = cherrypy.response.headers["Location"]
1127 outdata = {"id": _id, "filter": indata["filter"], "callbackUri": indata["CallbackUri"],
1128 "_links": link}
1129 cherrypy.response.status = HTTPStatus.CREATED.value
tiernof27c79b2018-03-12 17:08:42 +01001130 else:
tiernobdebce92019-07-01 15:36:49 +00001131 _id, op_id = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs,
1132 cherrypy.request.headers)
tiernob24258a2018-10-04 18:39:49 +02001133 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001134 outdata = {"id": _id}
tiernobdebce92019-07-01 15:36:49 +00001135 if op_id:
1136 outdata["op_id"] = op_id
1137 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernob24258a2018-10-04 18:39:49 +02001138 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tierno09c073e2018-04-26 13:36:48 +02001139
tiernoc94c3df2018-02-09 15:38:54 +01001140 elif method == "DELETE":
1141 if not _id:
tierno701018c2019-06-25 11:13:14 +00001142 outdata = self.engine.del_item_list(engine_session, engine_topic, kwargs)
tierno09c073e2018-04-26 13:36:48 +02001143 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +01001144 else: # len(args) > 1
tierno22577432020-04-08 15:16:57 +00001145 # for NS NSI generate an operation
1146 op_id = None
tierno701018c2019-06-25 11:13:14 +00001147 if topic == "ns_instances_content" and not engine_session["force"]:
tiernob24258a2018-10-04 18:39:49 +02001148 nslcmop_desc = {
1149 "lcmOperationType": "terminate",
1150 "nsInstanceId": _id,
1151 "autoremove": True
1152 }
tierno22577432020-04-08 15:16:57 +00001153 op_id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", nslcmop_desc, kwargs)
1154 if op_id:
1155 outdata = {"_id": op_id}
tierno701018c2019-06-25 11:13:14 +00001156 elif topic == "netslice_instances_content" and not engine_session["force"]:
garciadeblas9750c5a2018-10-15 16:20:35 +02001157 nsilcmop_desc = {
1158 "lcmOperationType": "terminate",
Felipe Vicens126af572019-06-05 19:13:04 +02001159 "netsliceInstanceId": _id,
garciadeblas9750c5a2018-10-15 16:20:35 +02001160 "autoremove": True
1161 }
tierno22577432020-04-08 15:16:57 +00001162 op_id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", nsilcmop_desc, None)
1163 if op_id:
1164 outdata = {"_id": op_id}
1165 # if there is not any deletion in process, delete
1166 if not op_id:
1167 op_id = self.engine.del_item(engine_session, engine_topic, _id)
1168 if op_id:
1169 outdata = {"op_id": op_id}
1170 cherrypy.response.status = HTTPStatus.ACCEPTED.value if op_id else HTTPStatus.NO_CONTENT.value
tierno09c073e2018-04-26 13:36:48 +02001171
tierno7ae10112018-05-18 14:36:02 +02001172 elif method in ("PUT", "PATCH"):
tiernobdebce92019-07-01 15:36:49 +00001173 op_id = None
tierno701018c2019-06-25 11:13:14 +00001174 if not indata and not kwargs and not engine_session.get("set_project"):
tiernoc94c3df2018-02-09 15:38:54 +01001175 raise NbiException("Nothing to update. Provide payload and/or query string",
1176 HTTPStatus.BAD_REQUEST)
garciadeblas9750c5a2018-10-15 16:20:35 +02001177 if item in ("nsd_content", "package_content", "nst_content") and method == "PUT":
tierno701018c2019-06-25 11:13:14 +00001178 completed = self.engine.upload_content(engine_session, engine_topic, _id, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +01001179 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +01001180 if not completed:
1181 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +01001182 else:
tiernobdebce92019-07-01 15:36:49 +00001183 op_id = self.engine.edit_item(engine_session, engine_topic, _id, indata, kwargs)
1184
1185 if op_id:
1186 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1187 outdata = {"op_id": op_id}
1188 else:
1189 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
1190 outdata = None
tiernoc94c3df2018-02-09 15:38:54 +01001191 else:
1192 raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tiernoa6bb45d2019-06-14 09:45:39 +00001193
1194 # if Role information changes, it is needed to reload the information of roles
1195 if topic == "roles" and method != "GET":
1196 self.authenticator.load_operation_to_allowed_roles()
delacruzramoad682a52019-12-10 16:26:34 +01001197
1198 if topic == "projects" and method == "DELETE" \
1199 or topic in ["users", "roles"] and method in ["PUT", "PATCH", "DELETE"]:
1200 self.authenticator.remove_token_from_cache()
1201
tierno701018c2019-06-25 11:13:14 +00001202 return self._format_out(outdata, token_info, _format)
tiernob24258a2018-10-04 18:39:49 +02001203 except Exception as e:
tierno36ec8602018-11-02 17:27:11 +01001204 if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException,
delacruzramo01b15d32019-07-02 14:37:47 +02001205 ValidationError, AuthconnException)):
tiernob24258a2018-10-04 18:39:49 +02001206 http_code_value = cherrypy.response.status = e.http_code.value
1207 http_code_name = e.http_code.name
1208 cherrypy.log("Exception {}".format(e))
1209 else:
1210 http_code_value = cherrypy.response.status = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Felipe Vicense36ab852018-11-23 14:12:09 +01001211 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
tiernob24258a2018-10-04 18:39:49 +02001212 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +02001213 if hasattr(outdata, "close"): # is an open file
1214 outdata.close()
tiernob24258a2018-10-04 18:39:49 +02001215 error_text = str(e)
1216 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +02001217 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +02001218 try:
tiernocc103432018-10-19 14:10:35 +02001219 if rollback_item.get("operation") == "set":
1220 self.engine.db.set_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
1221 rollback_item["content"], fail_on_empty=False)
preethika.p329b8182020-04-22 12:25:39 +05301222 elif rollback_item.get("operation") == "del_list":
1223 self.engine.db.del_list(rollback_item["topic"], rollback_item["filter"],
1224 fail_on_empty=False)
tiernocc103432018-10-19 14:10:35 +02001225 else:
tiernoe8631782018-12-21 13:31:52 +00001226 self.engine.db.del_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
1227 fail_on_empty=False)
tierno3ace63c2018-05-03 17:51:43 +02001228 except Exception as e2:
tiernob24258a2018-10-04 18:39:49 +02001229 rollback_error_text = "Rollback Exception {}: {}".format(rollback_item, e2)
1230 cherrypy.log(rollback_error_text)
1231 error_text += ". " + rollback_error_text
1232 # if isinstance(e, MsgException):
1233 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1234 # engine_topic[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +01001235 problem_details = {
tiernob24258a2018-10-04 18:39:49 +02001236 "code": http_code_name,
1237 "status": http_code_value,
1238 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +01001239 }
tierno701018c2019-06-25 11:13:14 +00001240 return self._format_out(problem_details, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001241 # raise cherrypy.HTTPError(e.http_code.value, str(e))
tiernoa5035702019-07-29 08:54:42 +00001242 finally:
1243 if token_info:
1244 self._format_login(token_info)
1245 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
1246 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1247 if outdata.get(logging_id):
1248 cherrypy.request.login += ";{}={}".format(logging_id, outdata[logging_id][:36])
tiernoc94c3df2018-02-09 15:38:54 +01001249
1250
tiernoc94c3df2018-02-09 15:38:54 +01001251def _start_service():
1252 """
1253 Callback function called when cherrypy.engine starts
1254 Override configuration with env variables
1255 Set database, storage, message configuration
1256 Init database with admin/admin user password
1257 """
tierno932499c2019-01-28 17:28:10 +00001258 global nbi_server
1259 global subscription_thread
tiernoc94c3df2018-02-09 15:38:54 +01001260 cherrypy.log.error("Starting osm_nbi")
1261 # update general cherrypy configuration
1262 update_dict = {}
1263
1264 engine_config = cherrypy.tree.apps['/osm'].config
1265 for k, v in environ.items():
1266 if not k.startswith("OSMNBI_"):
1267 continue
tiernoe1281182018-05-22 12:24:36 +02001268 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +01001269 if not k2:
1270 continue
1271 try:
1272 # update static configuration
1273 if k == 'OSMNBI_STATIC_DIR':
1274 engine_config["/static"]['tools.staticdir.dir'] = v
1275 engine_config["/static"]['tools.staticdir.on'] = True
1276 elif k == 'OSMNBI_SOCKET_PORT' or k == 'OSMNBI_SERVER_PORT':
1277 update_dict['server.socket_port'] = int(v)
1278 elif k == 'OSMNBI_SOCKET_HOST' or k == 'OSMNBI_SERVER_HOST':
1279 update_dict['server.socket_host'] = v
tiernof5298be2018-05-16 14:43:57 +02001280 elif k1 in ("server", "test", "auth", "log"):
1281 update_dict[k1 + '.' + k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001282 elif k1 in ("message", "database", "storage", "authentication"):
tiernof5298be2018-05-16 14:43:57 +02001283 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001284 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +01001285 engine_config[k1][k2] = int(v)
1286 else:
1287 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001288
tiernoc94c3df2018-02-09 15:38:54 +01001289 except ValueError as e:
1290 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1291 except Exception as e:
1292 cherrypy.log.warn("skipping environ '{}' on exception '{}'".format(k, e))
1293
1294 if update_dict:
1295 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +02001296 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001297
1298 # logging cherrypy
1299 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1300 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
1301 logger_server = logging.getLogger("cherrypy.error")
1302 logger_access = logging.getLogger("cherrypy.access")
1303 logger_cherry = logging.getLogger("cherrypy")
1304 logger_nbi = logging.getLogger("nbi")
1305
tiernof5298be2018-05-16 14:43:57 +02001306 if "log.file" in engine_config["global"]:
1307 file_handler = logging.handlers.RotatingFileHandler(engine_config["global"]["log.file"],
tiernoc94c3df2018-02-09 15:38:54 +01001308 maxBytes=100e6, backupCount=9, delay=0)
1309 file_handler.setFormatter(log_formatter_simple)
1310 logger_cherry.addHandler(file_handler)
1311 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +02001312 # log always to standard output
1313 for format_, logger in {"nbi.server %(filename)s:%(lineno)s": logger_server,
1314 "nbi.access %(filename)s:%(lineno)s": logger_access,
1315 "%(name)s %(filename)s:%(lineno)s": logger_nbi
1316 }.items():
1317 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
1318 log_formatter_cherry = logging.Formatter(log_format_cherry, datefmt='%Y-%m-%dT%H:%M:%S')
1319 str_handler = logging.StreamHandler()
1320 str_handler.setFormatter(log_formatter_cherry)
1321 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +01001322
tiernof5298be2018-05-16 14:43:57 +02001323 if engine_config["global"].get("log.level"):
1324 logger_cherry.setLevel(engine_config["global"]["log.level"])
1325 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +01001326
1327 # logging other modules
1328 for k1, logname in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
1329 engine_config[k1]["logger_name"] = logname
1330 logger_module = logging.getLogger(logname)
1331 if "logfile" in engine_config[k1]:
1332 file_handler = logging.handlers.RotatingFileHandler(engine_config[k1]["logfile"],
tierno2236d202018-05-16 19:05:16 +02001333 maxBytes=100e6, backupCount=9, delay=0)
tiernoc94c3df2018-02-09 15:38:54 +01001334 file_handler.setFormatter(log_formatter_simple)
1335 logger_module.addHandler(file_handler)
1336 if "loglevel" in engine_config[k1]:
1337 logger_module.setLevel(engine_config[k1]["loglevel"])
1338 # TODO add more entries, e.g.: storage
1339 cherrypy.tree.apps['/osm'].root.engine.start(engine_config)
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001340 cherrypy.tree.apps['/osm'].root.authenticator.start(engine_config)
tiernod985a8d2018-10-19 14:12:28 +02001341 cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version)
1342 cherrypy.tree.apps['/osm'].root.authenticator.init_db(target_version=auth_database_version)
tiernobee508e2019-01-21 11:21:49 +00001343
tierno932499c2019-01-28 17:28:10 +00001344 # start subscriptions thread:
1345 subscription_thread = SubscriptionThread(config=engine_config, engine=nbi_server.engine)
1346 subscription_thread.start()
1347 # Do not capture except SubscriptionException
1348
tiernob2e48bd2020-02-04 15:47:18 +00001349 backend = engine_config["authentication"]["backend"]
1350 cherrypy.log.error("Starting OSM NBI Version '{} {}' with '{}' authentication backend"
1351 .format(nbi_version, nbi_version_date, backend))
tiernoc94c3df2018-02-09 15:38:54 +01001352
1353
1354def _stop_service():
1355 """
1356 Callback function called when cherrypy.engine stops
1357 TODO: Ending database connections.
1358 """
tierno932499c2019-01-28 17:28:10 +00001359 global subscription_thread
tierno65ca36d2019-02-12 19:27:52 +01001360 if subscription_thread:
1361 subscription_thread.terminate()
tierno932499c2019-01-28 17:28:10 +00001362 subscription_thread = None
tiernoc94c3df2018-02-09 15:38:54 +01001363 cherrypy.tree.apps['/osm'].root.engine.stop()
1364 cherrypy.log.error("Stopping osm_nbi")
1365
tierno2236d202018-05-16 19:05:16 +02001366
tiernof5298be2018-05-16 14:43:57 +02001367def nbi(config_file):
tierno932499c2019-01-28 17:28:10 +00001368 global nbi_server
tiernoc94c3df2018-02-09 15:38:54 +01001369 # conf = {
1370 # '/': {
1371 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1372 # 'tools.sessions.on': True,
1373 # 'tools.response_headers.on': True,
1374 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1375 # }
1376 # }
1377 # cherrypy.Server.ssl_module = 'builtin'
1378 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1379 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1380 # cherrypy.Server.thread_pool = 10
1381 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1382
1383 # cherrypy.config.update({'tools.auth_basic.on': True,
1384 # 'tools.auth_basic.realm': 'localhost',
1385 # 'tools.auth_basic.checkpassword': validate_password})
tierno932499c2019-01-28 17:28:10 +00001386 nbi_server = Server()
tiernoc94c3df2018-02-09 15:38:54 +01001387 cherrypy.engine.subscribe('start', _start_service)
1388 cherrypy.engine.subscribe('stop', _stop_service)
tierno932499c2019-01-28 17:28:10 +00001389 cherrypy.quickstart(nbi_server, '/osm', config_file)
tiernof5298be2018-05-16 14:43:57 +02001390
1391
1392def usage():
1393 print("""Usage: {} [options]
1394 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1395 -h|--help: shows this help
1396 """.format(sys.argv[0]))
tierno2236d202018-05-16 19:05:16 +02001397 # --log-socket-host HOST: send logs to this host")
1398 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +01001399
1400
1401if __name__ == '__main__':
tiernof5298be2018-05-16 14:43:57 +02001402 try:
1403 # load parameters and configuration
1404 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
1405 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1406 config_file = None
1407 for o, a in opts:
1408 if o in ("-h", "--help"):
1409 usage()
1410 sys.exit()
1411 elif o in ("-c", "--config"):
1412 config_file = a
1413 # elif o == "--log-socket-port":
1414 # log_socket_port = a
1415 # elif o == "--log-socket-host":
1416 # log_socket_host = a
1417 # elif o == "--log-file":
1418 # log_file = a
1419 else:
1420 assert False, "Unhandled option"
1421 if config_file:
1422 if not path.isfile(config_file):
1423 print("configuration file '{}' that not exist".format(config_file), file=sys.stderr)
1424 exit(1)
1425 else:
1426 for config_file in (__file__[:__file__.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
1427 if path.isfile(config_file):
1428 break
1429 else:
1430 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys.stderr)
1431 exit(1)
1432 nbi(config_file)
1433 except getopt.GetoptError as e:
1434 print(str(e), file=sys.stderr)
1435 # usage()
1436 exit(1)