blob: 2d2d6355ac77764275b3b7b6647f7c8f40512466 [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
tiernoc94c3df2018-02-09 15:38:54 +0100118
garciadeblas9750c5a2018-10-15 16:20:35 +0200119 /nst/v1 O O
120 /netslice_templates_content O O
121 /<nstInfoId> O O O O
122 /netslice_templates O O
123 /<nstInfoId> O O O
124 /nst_content O O
125 /nst O
126 /artifacts[/<artifactPath>] O
127 /subscriptions X X
128 /<subscriptionId> X X
129
130 /nsilcm/v1
131 /netslice_instances_content O O
132 /<SliceInstanceId> O O
133 /netslice_instances O O
134 /<SliceInstanceId> O O
135 instantiate O
136 terminate O
137 action O
138 /nsi_lcm_op_occs O O
139 /<nsiLcmOpOccId> O O O
140 /subscriptions X X
141 /<subscriptionId> X X
142
tierno2236d202018-05-16 19:05:16 +0200143query string:
144 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
tierno36ec8602018-11-02 17:27:11 +0100145 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
146 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
147 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
148 attrName := string
tierno2236d202018-05-16 19:05:16 +0200149 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
150 item of the array, that is, pass if any item of the array pass the filter.
151 It allows both ne and neq for not equal
152 TODO: 4.3.3 Attribute selectors
153 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
tiernoc94c3df2018-02-09 15:38:54 +0100154 (none) … same as “exclude_default”
155 all_fields … all attributes.
tierno2236d202018-05-16 19:05:16 +0200156 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
157 conditionally mandatory, and that are not provided in <list>.
158 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
159 are not conditionally mandatory, and that are provided in <list>.
160 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
161 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
162 the particular resource
163 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
164 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
165 present specification for the particular resource, but that are not part of <list>
tierno65ca36d2019-02-12 19:27:52 +0100166 Additionally it admits some administrator values:
167 FORCE: To force operations skipping dependency checkings
168 ADMIN: To act as an administrator or a different project
169 PUBLIC: To get public descriptors or set a descriptor as public
170 SET_PROJECT: To make a descriptor available for other project
171
tiernoc94c3df2018-02-09 15:38:54 +0100172Header field name Reference Example Descriptions
173 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
174 This header field shall be present if the response is expected to have a non-empty message body.
175 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
176 This header field shall be present if the request has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200177 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
178 Details are specified in clause 4.5.3.
tiernoc94c3df2018-02-09 15:38:54 +0100179 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
180Header field name Reference Example Descriptions
181 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
182 This header field shall be present if the response has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200183 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
184 new resource has been created.
tiernoc94c3df2018-02-09 15:38:54 +0100185 This header field shall be present if the response status code is 201 or 3xx.
tierno2236d202018-05-16 19:05:16 +0200186 In the present document this header field is also used if the response status code is 202 and a new resource was
187 created.
188 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
189 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
190 token.
191 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
192 certain resources.
193 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
194 response, and the total length of the file.
tiernoc94c3df2018-02-09 15:38:54 +0100195 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
tiernoc94c3df2018-02-09 15:38:54 +0100196"""
197
tierno701018c2019-06-25 11:13:14 +0000198valid_query_string = ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
199# ^ Contains possible administrative query string words:
200# ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
201# (not owned by my session project).
202# PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
203# FORCE=True(by default)|False: Force edition/deletion operations
204# SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
205
206valid_url_methods = {
207 # contains allowed URL and methods, and the role_permission name
208 "admin": {
209 "v1": {
210 "tokens": {"METHODS": ("GET", "POST", "DELETE"),
211 "ROLE_PERMISSION": "tokens:",
212 "<ID>": {"METHODS": ("GET", "DELETE"),
213 "ROLE_PERMISSION": "tokens:id:"
214 }
215 },
216 "users": {"METHODS": ("GET", "POST"),
217 "ROLE_PERMISSION": "users:",
delacruzramo1459d602019-10-03 14:22:00 +0200218 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno701018c2019-06-25 11:13:14 +0000219 "ROLE_PERMISSION": "users:id:"
220 }
221 },
222 "projects": {"METHODS": ("GET", "POST"),
223 "ROLE_PERMISSION": "projects:",
delacruzramo1459d602019-10-03 14:22:00 +0200224 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno701018c2019-06-25 11:13:14 +0000225 "ROLE_PERMISSION": "projects:id:"}
226 },
227 "roles": {"METHODS": ("GET", "POST"),
228 "ROLE_PERMISSION": "roles:",
delacruzramo1459d602019-10-03 14:22:00 +0200229 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno701018c2019-06-25 11:13:14 +0000230 "ROLE_PERMISSION": "roles:id:"
231 }
232 },
233 "vims": {"METHODS": ("GET", "POST"),
234 "ROLE_PERMISSION": "vims:",
delacruzramo1459d602019-10-03 14:22:00 +0200235 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno701018c2019-06-25 11:13:14 +0000236 "ROLE_PERMISSION": "vims:id:"
237 }
238 },
239 "vim_accounts": {"METHODS": ("GET", "POST"),
240 "ROLE_PERMISSION": "vim_accounts:",
delacruzramo1459d602019-10-03 14:22:00 +0200241 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno701018c2019-06-25 11:13:14 +0000242 "ROLE_PERMISSION": "vim_accounts:id:"
243 }
244 },
245 "wim_accounts": {"METHODS": ("GET", "POST"),
246 "ROLE_PERMISSION": "wim_accounts:",
delacruzramo1459d602019-10-03 14:22:00 +0200247 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno701018c2019-06-25 11:13:14 +0000248 "ROLE_PERMISSION": "wim_accounts:id:"
249 }
250 },
251 "sdns": {"METHODS": ("GET", "POST"),
252 "ROLE_PERMISSION": "sdn_controllers:",
delacruzramo1459d602019-10-03 14:22:00 +0200253 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno701018c2019-06-25 11:13:14 +0000254 "ROLE_PERMISSION": "sdn_controllers:id:"
255 }
256 },
delacruzramofe598fe2019-10-23 18:25:11 +0200257 "k8sclusters": {"METHODS": ("GET", "POST"),
258 "ROLE_PERMISSION": "k8sclusters:",
delacruzramo549dda62019-11-14 09:57:35 +0100259 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
delacruzramofe598fe2019-10-23 18:25:11 +0200260 "ROLE_PERMISSION": "k8sclusters:id:"
261 }
262 },
263 "k8srepos": {"METHODS": ("GET", "POST"),
264 "ROLE_PERMISSION": "k8srepos:",
265 "<ID>": {"METHODS": ("GET", "DELETE"),
266 "ROLE_PERMISSION": "k8srepos:id:"
267 }
268 },
269
tierno701018c2019-06-25 11:13:14 +0000270 }
271 },
272 "pdu": {
273 "v1": {
274 "pdu_descriptors": {"METHODS": ("GET", "POST"),
275 "ROLE_PERMISSION": "pduds:",
276 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
277 "ROLE_PERMISSION": "pduds:id:"
278 }
279 },
280 }
281 },
282 "nsd": {
283 "v1": {
284 "ns_descriptors_content": {"METHODS": ("GET", "POST"),
285 "ROLE_PERMISSION": "nsds:",
286 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
287 "ROLE_PERMISSION": "nsds:id:"
288 }
289 },
290 "ns_descriptors": {"METHODS": ("GET", "POST"),
291 "ROLE_PERMISSION": "nsds:",
292 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
293 "ROLE_PERMISSION": "nsds:id:",
294 "nsd_content": {"METHODS": ("GET", "PUT"),
295 "ROLE_PERMISSION": "nsds:id:content:",
296 },
297 "nsd": {"METHODS": ("GET",), # descriptor inside package
298 "ROLE_PERMISSION": "nsds:id:content:"
299 },
300 "artifacts": {"*": {"METHODS": ("GET",),
301 "ROLE_PERMISSION": "nsds:id:nsd_artifact:"
302 }
303 }
304 }
305 },
306 "pnf_descriptors": {"TODO": ("GET", "POST"),
307 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
308 "pnfd_content": {"TODO": ("GET", "PUT")}
309 }
310 },
311 "subscriptions": {"TODO": ("GET", "POST"),
312 "<ID>": {"TODO": ("GET", "DELETE")}
313 },
314 }
315 },
316 "vnfpkgm": {
317 "v1": {
318 "vnf_packages_content": {"METHODS": ("GET", "POST"),
319 "ROLE_PERMISSION": "vnfds:",
320 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
321 "ROLE_PERMISSION": "vnfds:id:"}
322 },
323 "vnf_packages": {"METHODS": ("GET", "POST"),
324 "ROLE_PERMISSION": "vnfds:",
325 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
326 "ROLE_PERMISSION": "vnfds:id:",
327 "package_content": {"METHODS": ("GET", "PUT"), # package
328 "ROLE_PERMISSION": "vnfds:id:",
329 "upload_from_uri": {"METHODS": (),
330 "TODO": ("POST", ),
331 "ROLE_PERMISSION": "vnfds:id:upload:"
332 }
333 },
334 "vnfd": {"METHODS": ("GET", ), # descriptor inside package
335 "ROLE_PERMISSION": "vnfds:id:content:"
336 },
337 "artifacts": {"*": {"METHODS": ("GET", ),
338 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:"
339 }
delacruzramo271d2002019-12-02 21:00:37 +0100340 },
341 "action": {"METHODS": ("POST", ),
342 "ROLE_PERMISSION": "vnfds:id:action:"
343 },
tierno701018c2019-06-25 11:13:14 +0000344 }
345 },
346 "subscriptions": {"TODO": ("GET", "POST"),
347 "<ID>": {"TODO": ("GET", "DELETE")}
348 },
delacruzramo271d2002019-12-02 21:00:37 +0100349 "vnfpkg_op_occs": {"METHODS": ("GET", ),
350 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
351 "<ID>": {"METHODS": ("GET", ),
352 "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"
353 }
354 },
tierno701018c2019-06-25 11:13:14 +0000355 }
356 },
357 "nslcm": {
358 "v1": {
359 "ns_instances_content": {"METHODS": ("GET", "POST"),
360 "ROLE_PERMISSION": "ns_instances:",
361 "<ID>": {"METHODS": ("GET", "DELETE"),
362 "ROLE_PERMISSION": "ns_instances:id:"
363 }
364 },
365 "ns_instances": {"METHODS": ("GET", "POST"),
366 "ROLE_PERMISSION": "ns_instances:",
367 "<ID>": {"METHODS": ("GET", "DELETE"),
368 "ROLE_PERMISSION": "ns_instances:id:",
369 "scale": {"METHODS": ("POST",),
370 "ROLE_PERMISSION": "ns_instances:id:scale:"
371 },
372 "terminate": {"METHODS": ("POST",),
373 "ROLE_PERMISSION": "ns_instances:id:terminate:"
374 },
375 "instantiate": {"METHODS": ("POST",),
376 "ROLE_PERMISSION": "ns_instances:id:instantiate:"
377 },
378 "action": {"METHODS": ("POST",),
379 "ROLE_PERMISSION": "ns_instances:id:action:"
380 },
381 }
382 },
383 "ns_lcm_op_occs": {"METHODS": ("GET",),
384 "ROLE_PERMISSION": "ns_instances:opps:",
385 "<ID>": {"METHODS": ("GET",),
386 "ROLE_PERMISSION": "ns_instances:opps:id:"
387 },
388 },
389 "vnfrs": {"METHODS": ("GET",),
390 "ROLE_PERMISSION": "vnf_instances:",
391 "<ID>": {"METHODS": ("GET",),
392 "ROLE_PERMISSION": "vnf_instances:id:"
393 }
394 },
395 "vnf_instances": {"METHODS": ("GET",),
396 "ROLE_PERMISSION": "vnf_instances:",
397 "<ID>": {"METHODS": ("GET",),
398 "ROLE_PERMISSION": "vnf_instances:id:"
399 }
400 },
401 }
402 },
403 "nst": {
404 "v1": {
405 "netslice_templates_content": {"METHODS": ("GET", "POST"),
406 "ROLE_PERMISSION": "slice_templates:",
407 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
408 "ROLE_PERMISSION": "slice_templates:id:", }
409 },
410 "netslice_templates": {"METHODS": ("GET", "POST"),
411 "ROLE_PERMISSION": "slice_templates:",
412 "<ID>": {"METHODS": ("GET", "DELETE"),
413 "TODO": ("PATCH",),
414 "ROLE_PERMISSION": "slice_templates:id:",
415 "nst_content": {"METHODS": ("GET", "PUT"),
416 "ROLE_PERMISSION": "slice_templates:id:content:"
417 },
418 "nst": {"METHODS": ("GET",), # descriptor inside package
419 "ROLE_PERMISSION": "slice_templates:id:content:"
420 },
421 "artifacts": {"*": {"METHODS": ("GET",),
422 "ROLE_PERMISSION": "slice_templates:id:content:"
423 }
424 }
425 }
426 },
427 "subscriptions": {"TODO": ("GET", "POST"),
428 "<ID>": {"TODO": ("GET", "DELETE")}
429 },
430 }
431 },
432 "nsilcm": {
433 "v1": {
434 "netslice_instances_content": {"METHODS": ("GET", "POST"),
435 "ROLE_PERMISSION": "slice_instances:",
436 "<ID>": {"METHODS": ("GET", "DELETE"),
437 "ROLE_PERMISSION": "slice_instances:id:"
438 }
439 },
440 "netslice_instances": {"METHODS": ("GET", "POST"),
441 "ROLE_PERMISSION": "slice_instances:",
442 "<ID>": {"METHODS": ("GET", "DELETE"),
443 "ROLE_PERMISSION": "slice_instances:id:",
444 "terminate": {"METHODS": ("POST",),
445 "ROLE_PERMISSION": "slice_instances:id:terminate:"
446 },
447 "instantiate": {"METHODS": ("POST",),
448 "ROLE_PERMISSION": "slice_instances:id:instantiate:"
449 },
450 "action": {"METHODS": ("POST",),
451 "ROLE_PERMISSION": "slice_instances:id:action:"
452 },
453 }
454 },
455 "nsi_lcm_op_occs": {"METHODS": ("GET",),
456 "ROLE_PERMISSION": "slice_instances:opps:",
457 "<ID>": {"METHODS": ("GET",),
458 "ROLE_PERMISSION": "slice_instances:opps:id:",
459 },
460 },
461 }
462 },
463 "nspm": {
464 "v1": {
465 "pm_jobs": {
466 "<ID>": {
467 "reports": {
468 "<ID>": {"METHODS": ("GET",),
469 "ROLE_PERMISSION": "reports:id:",
470 }
471 }
472 },
473 },
474 },
475 },
476}
477
tiernoc94c3df2018-02-09 15:38:54 +0100478
479class NbiException(Exception):
480
481 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
482 Exception.__init__(self, message)
483 self.http_code = http_code
484
485
486class Server(object):
487 instance = 0
488 # to decode bytes to str
489 reader = getreader("utf-8")
490
491 def __init__(self):
492 self.instance += 1
tierno701018c2019-06-25 11:13:14 +0000493 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
delacruzramoad682a52019-12-10 16:26:34 +0100494 self.engine = Engine(self.authenticator)
tiernoc94c3df2018-02-09 15:38:54 +0100495
tiernoc94c3df2018-02-09 15:38:54 +0100496 def _format_in(self, kwargs):
497 try:
498 indata = None
499 if cherrypy.request.body.length:
500 error_text = "Invalid input format "
501
502 if "Content-Type" in cherrypy.request.headers:
503 if "application/json" in cherrypy.request.headers["Content-Type"]:
504 error_text = "Invalid json format "
505 indata = json.load(self.reader(cherrypy.request.body))
gcalvinode4adfe2018-10-30 11:46:09 +0100506 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100507 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
508 error_text = "Invalid yaml format "
delacruzramob19cadc2019-10-08 10:18:02 +0200509 indata = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
gcalvinode4adfe2018-10-30 11:46:09 +0100510 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100511 elif "application/binary" in cherrypy.request.headers["Content-Type"] or \
512 "application/gzip" in cherrypy.request.headers["Content-Type"] or \
tiernof27c79b2018-03-12 17:08:42 +0100513 "application/zip" in cherrypy.request.headers["Content-Type"] or \
514 "text/plain" in cherrypy.request.headers["Content-Type"]:
515 indata = cherrypy.request.body # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100516 elif "multipart/form-data" in cherrypy.request.headers["Content-Type"]:
517 if "descriptor_file" in kwargs:
518 filecontent = kwargs.pop("descriptor_file")
519 if not filecontent.file:
520 raise NbiException("empty file or content", HTTPStatus.BAD_REQUEST)
tiernof27c79b2018-03-12 17:08:42 +0100521 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100522 if filecontent.content_type.value:
523 cherrypy.request.headers["Content-Type"] = filecontent.content_type.value
524 else:
525 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
526 # "Only 'Content-Type' of type 'application/json' or
527 # 'application/yaml' for input format are available")
528 error_text = "Invalid yaml format "
delacruzramob19cadc2019-10-08 10:18:02 +0200529 indata = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
gcalvinode4adfe2018-10-30 11:46:09 +0100530 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100531 else:
532 error_text = "Invalid yaml format "
delacruzramob19cadc2019-10-08 10:18:02 +0200533 indata = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
gcalvinode4adfe2018-10-30 11:46:09 +0100534 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100535 if not indata:
536 indata = {}
537
tiernoc94c3df2018-02-09 15:38:54 +0100538 format_yaml = False
539 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
540 format_yaml = True
541
542 for k, v in kwargs.items():
543 if isinstance(v, str):
544 if v == "":
545 kwargs[k] = None
546 elif format_yaml:
547 try:
delacruzramob19cadc2019-10-08 10:18:02 +0200548 kwargs[k] = yaml.load(v, Loader=yaml.SafeLoader)
tiernoe1281182018-05-22 12:24:36 +0200549 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100550 pass
551 elif k.endswith(".gt") or k.endswith(".lt") or k.endswith(".gte") or k.endswith(".lte"):
552 try:
553 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200554 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100555 try:
556 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200557 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100558 pass
559 elif v.find(",") > 0:
560 kwargs[k] = v.split(",")
561 elif isinstance(v, (list, tuple)):
562 for index in range(0, len(v)):
563 if v[index] == "":
564 v[index] = None
565 elif format_yaml:
566 try:
delacruzramob19cadc2019-10-08 10:18:02 +0200567 v[index] = yaml.load(v[index], Loader=yaml.SafeLoader)
tiernoe1281182018-05-22 12:24:36 +0200568 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100569 pass
570
tiernof27c79b2018-03-12 17:08:42 +0100571 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100572 except (ValueError, yaml.YAMLError) as exc:
573 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
574 except KeyError as exc:
575 raise NbiException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
tiernob92094f2018-05-11 13:44:22 +0200576 except Exception as exc:
577 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100578
579 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000580 def _format_out(data, token_info=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100581 """
582 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100583 :param data: response to be sent. Can be a dict, text or file
tierno701018c2019-06-25 11:13:14 +0000584 :param token_info: Contains among other username and project
tierno5792d7d2019-08-30 15:37:12 +0000585 :param _format: The format to be set as Content-Type if data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100586 :return: None
587 """
tierno0f98af52018-03-19 10:28:22 +0100588 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100589 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100590 if accept and "text/html" in accept:
tierno701018c2019-06-25 11:13:14 +0000591 return html.format(data, cherrypy.request, cherrypy.response, token_info)
tierno09c073e2018-04-26 13:36:48 +0200592 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100593 return
594 elif hasattr(data, "read"): # file object
595 if _format:
596 cherrypy.response.headers["Content-Type"] = _format
597 elif "b" in data.mode: # binariy asssumig zip
598 cherrypy.response.headers["Content-Type"] = 'application/zip'
599 else:
600 cherrypy.response.headers["Content-Type"] = 'text/plain'
601 # TODO check that cherrypy close file. If not implement pending things to close per thread next
602 return data
tierno0f98af52018-03-19 10:28:22 +0100603 if accept:
tiernoc94c3df2018-02-09 15:38:54 +0100604 if "application/json" in accept:
605 cherrypy.response.headers["Content-Type"] = 'application/json; charset=utf-8'
606 a = json.dumps(data, indent=4) + "\n"
607 return a.encode("utf8")
608 elif "text/html" in accept:
tierno701018c2019-06-25 11:13:14 +0000609 return html.format(data, cherrypy.request, cherrypy.response, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100610
tiernof27c79b2018-03-12 17:08:42 +0100611 elif "application/yaml" in accept or "*/*" in accept or "text/plain" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100612 pass
tiernof717cbe2018-12-03 16:35:42 +0000613 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
614 elif cherrypy.response.status >= 400:
tiernoc94c3df2018-02-09 15:38:54 +0100615 raise cherrypy.HTTPError(HTTPStatus.NOT_ACCEPTABLE.value,
616 "Only 'Accept' of type 'application/json' or 'application/yaml' "
617 "for output format are available")
618 cherrypy.response.headers["Content-Type"] = 'application/yaml'
619 return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False,
620 encoding='utf-8', allow_unicode=True) # , canonical=True, default_style='"'
621
622 @cherrypy.expose
623 def index(self, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000624 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100625 try:
626 if cherrypy.request.method == "GET":
tierno701018c2019-06-25 11:13:14 +0000627 token_info = self.authenticator.authorize()
delacruzramo01b15d32019-07-02 14:37:47 +0200628 outdata = token_info # Home page
tiernoc94c3df2018-02-09 15:38:54 +0100629 else:
630 raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value,
tierno2236d202018-05-16 19:05:16 +0200631 "Method {} not allowed for tokens".format(cherrypy.request.method))
tiernoc94c3df2018-02-09 15:38:54 +0100632
tierno701018c2019-06-25 11:13:14 +0000633 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100634
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100635 except (EngineException, AuthException) as e:
tierno701018c2019-06-25 11:13:14 +0000636 # cherrypy.log("index Exception {}".format(e))
tiernoc94c3df2018-02-09 15:38:54 +0100637 cherrypy.response.status = e.http_code.value
tierno701018c2019-06-25 11:13:14 +0000638 return self._format_out("Welcome to OSM!", token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100639
640 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200641 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200642 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200643 try:
644 if cherrypy.request.method != "GET":
645 raise NbiException("Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED)
646 elif args or kwargs:
647 raise NbiException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED)
tierno9c630112019-08-29 14:21:41 +0000648 # TODO include version of other modules, pick up from some kafka admin message
tierno5792d7d2019-08-30 15:37:12 +0000649 osm_nbi_version = {"version": nbi_version, "date": nbi_version_date}
650 return self._format_out(osm_nbi_version)
tierno55945e72018-04-06 16:40:27 +0200651 except NbiException as e:
652 cherrypy.response.status = e.http_code.value
653 problem_details = {
654 "code": e.http_code.name,
655 "status": e.http_code.value,
656 "detail": str(e),
657 }
658 return self._format_out(problem_details, None)
659
tiernoa5035702019-07-29 08:54:42 +0000660 @staticmethod
661 def _format_login(token_info):
662 """
663 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
664 log this information
665 :param token_info: Dictionary with token content
666 :return: None
667 """
668 cherrypy.request.login = token_info.get("username", "-")
669 if token_info.get("project_name"):
670 cherrypy.request.login += "/" + token_info["project_name"]
671 if token_info.get("id"):
672 cherrypy.request.login += ";session=" + token_info["id"][0:12]
673
tierno55945e72018-04-06 16:40:27 +0200674 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100675 def token(self, method, token_id=None, kwargs=None):
tierno701018c2019-06-25 11:13:14 +0000676 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100677 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +0100678 indata = self._format_in(kwargs)
679 if not isinstance(indata, dict):
680 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST)
tiernoa5035702019-07-29 08:54:42 +0000681
682 if method == "GET":
683 token_info = self.authenticator.authorize()
684 # for logging
685 self._format_login(token_info)
686 if token_id:
687 outdata = self.authenticator.get_token(token_info, token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100688 else:
tiernoa5035702019-07-29 08:54:42 +0000689 outdata = self.authenticator.get_token_list(token_info)
690 elif method == "POST":
691 try:
692 token_info = self.authenticator.authorize()
693 except Exception:
694 token_info = None
695 if kwargs:
696 indata.update(kwargs)
697 # This is needed to log the user when authentication fails
698 cherrypy.request.login = "{}".format(indata.get("username", "-"))
699 outdata = token_info = self.authenticator.new_token(token_info, indata, cherrypy.request.remote)
700 cherrypy.session['Authorization'] = outdata["_id"]
701 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
702 # for logging
703 self._format_login(token_info)
704
705 # cherrypy.response.cookie["Authorization"] = outdata["id"]
706 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
707 elif method == "DELETE":
708 if not token_id and "id" in kwargs:
709 token_id = kwargs["id"]
710 elif not token_id:
711 token_info = self.authenticator.authorize()
712 # for logging
713 self._format_login(token_info)
714 token_id = token_info["_id"]
715 outdata = self.authenticator.del_token(token_id)
716 token_info = None
717 cherrypy.session['Authorization'] = "logout"
718 # cherrypy.response.cookie["Authorization"] = token_id
719 # cherrypy.response.cookie["Authorization"]['expires'] = 0
720 else:
721 raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
722 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100723
724 @cherrypy.expose
725 def test(self, *args, **kwargs):
tierno4836bac2020-01-15 14:41:48 +0000726 if not cherrypy.config.get("server.enable_test") or (isinstance(cherrypy.config["server.enable_test"], str) and
727 cherrypy.config["server.enable_test"].lower() == "false"):
728 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value
729 return "test URL is disabled"
tiernoc94c3df2018-02-09 15:38:54 +0100730 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +0100731 if args and args[0] == "help":
tiernof717cbe2018-12-03 16:35:42 +0000732 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
tiernoe1281182018-05-22 12:24:36 +0200733 "sleep/<time>\nmessage/topic\n</pre></html>"
tiernof27c79b2018-03-12 17:08:42 +0100734
735 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +0100736 try:
737 # self.engine.load_dbase(cherrypy.request.app.config)
738 self.engine.create_admin()
739 return "Done. User 'admin', password 'admin' created"
740 except Exception:
741 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
742 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +0100743 elif args and args[0] == "file":
744 return cherrypy.lib.static.serve_file(cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1],
745 "text/plain", "attachment")
746 elif args and args[0] == "file2":
747 f_path = cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1]
748 f = open(f_path, "r")
749 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100750 return f
tierno55945e72018-04-06 16:40:27 +0200751
tiernof27c79b2018-03-12 17:08:42 +0100752 elif len(args) == 2 and args[0] == "db-clear":
tiernof717cbe2018-12-03 16:35:42 +0000753 deleted_info = self.engine.db.del_list(args[1], kwargs)
754 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
755 elif len(args) and args[0] == "fs-clear":
756 if len(args) >= 2:
757 folders = (args[1],)
758 else:
759 folders = self.engine.fs.dir_ls(".")
760 for folder in folders:
761 self.engine.fs.file_delete(folder)
762 return ",".join(folders) + " folders deleted\n"
tiernoc94c3df2018-02-09 15:38:54 +0100763 elif args and args[0] == "login":
764 if not cherrypy.request.headers.get("Authorization"):
765 cherrypy.response.headers["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
766 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
767 elif args and args[0] == "login2":
768 if not cherrypy.request.headers.get("Authorization"):
769 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
770 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
771 elif args and args[0] == "sleep":
772 sleep_time = 5
773 try:
774 sleep_time = int(args[1])
775 except Exception:
776 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
777 return self._format_out("Database already initialized")
778 thread_info = cherrypy.thread_data
779 print(thread_info)
780 time.sleep(sleep_time)
781 # thread_info
782 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +0200783 main_topic = args[1]
784 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +0100785 try:
tierno55945e72018-04-06 16:40:27 +0200786 if cherrypy.request.method == 'POST':
delacruzramob19cadc2019-10-08 10:18:02 +0200787 to_send = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
tierno55945e72018-04-06 16:40:27 +0200788 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +0200789 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +0200790 return_text += " {}: {}\n".format(k, v)
791 elif cherrypy.request.method == 'GET':
792 for k, v in kwargs.items():
delacruzramob19cadc2019-10-08 10:18:02 +0200793 self.engine.msg.write(main_topic, k, yaml.load(v), Loader=yaml.SafeLoader)
794 return_text += " {}: {}\n".format(k, yaml.load(v), Loader=yaml.SafeLoader)
tiernoc94c3df2018-02-09 15:38:54 +0100795 except Exception as e:
tierno55945e72018-04-06 16:40:27 +0200796 return_text += "Error: " + str(e)
797 return_text += "</pre></html>\n"
798 return return_text
tiernoc94c3df2018-02-09 15:38:54 +0100799
800 return_text = (
801 "<html><pre>\nheaders:\n args: {}\n".format(args) +
802 " kwargs: {}\n".format(kwargs) +
803 " headers: {}\n".format(cherrypy.request.headers) +
804 " path_info: {}\n".format(cherrypy.request.path_info) +
805 " query_string: {}\n".format(cherrypy.request.query_string) +
806 " session: {}\n".format(cherrypy.session) +
807 " cookie: {}\n".format(cherrypy.request.cookie) +
808 " method: {}\n".format(cherrypy.request.method) +
tiernof717cbe2018-12-03 16:35:42 +0000809 " session: {}\n".format(cherrypy.session.get('fieldname')) +
tiernoc94c3df2018-02-09 15:38:54 +0100810 " body:\n")
811 return_text += " length: {}\n".format(cherrypy.request.body.length)
812 if cherrypy.request.body.length:
813 return_text += " content: {}\n".format(
814 str(cherrypy.request.body.read(int(cherrypy.request.headers.get('Content-Length', 0)))))
815 if thread_info:
816 return_text += "thread: {}\n".format(thread_info)
817 return_text += "</pre></html>"
818 return return_text
819
tierno701018c2019-06-25 11:13:14 +0000820 @staticmethod
821 def _check_valid_url_method(method, *args):
tiernof27c79b2018-03-12 17:08:42 +0100822 if len(args) < 3:
tiernob24258a2018-10-04 18:39:49 +0200823 raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus.METHOD_NOT_ALLOWED)
tiernof27c79b2018-03-12 17:08:42 +0100824
tierno701018c2019-06-25 11:13:14 +0000825 reference = valid_url_methods
tiernof27c79b2018-03-12 17:08:42 +0100826 for arg in args:
827 if arg is None:
828 break
829 if not isinstance(reference, dict):
830 raise NbiException("URL contains unexpected extra items '{}'".format(arg),
831 HTTPStatus.METHOD_NOT_ALLOWED)
832
833 if arg in reference:
834 reference = reference[arg]
835 elif "<ID>" in reference:
836 reference = reference["<ID>"]
837 elif "*" in reference:
838 reference = reference["*"]
839 break
840 else:
841 raise NbiException("Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED)
842 if "TODO" in reference and method in reference["TODO"]:
843 raise NbiException("Method {} not supported yet for this URL".format(method), HTTPStatus.NOT_IMPLEMENTED)
tierno2236d202018-05-16 19:05:16 +0200844 elif "METHODS" in reference and method not in reference["METHODS"]:
tiernof27c79b2018-03-12 17:08:42 +0100845 raise NbiException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tierno701018c2019-06-25 11:13:14 +0000846 return reference["ROLE_PERMISSION"] + method.lower()
tiernof27c79b2018-03-12 17:08:42 +0100847
848 @staticmethod
tiernob24258a2018-10-04 18:39:49 +0200849 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +0100850 """
851 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +0200852 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +0100853 :param version:
tiernob24258a2018-10-04 18:39:49 +0200854 :param topic:
tiernof27c79b2018-03-12 17:08:42 +0100855 :param id:
856 :return: None
857 """
858 # 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 +0200859 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(main_topic, version, topic, id)
tiernof27c79b2018-03-12 17:08:42 +0100860 return
861
tierno65ca36d2019-02-12 19:27:52 +0100862 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000863 def _extract_query_string_operations(kwargs, method):
864 """
865
866 :param kwargs:
867 :return:
868 """
869 query_string_operations = []
870 if kwargs:
871 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
872 if qs in kwargs and kwargs[qs].lower() != "false":
873 query_string_operations.append(qs.lower() + ":" + method.lower())
874 return query_string_operations
875
876 @staticmethod
877 def _manage_admin_query(token_info, kwargs, method, _id):
tierno65ca36d2019-02-12 19:27:52 +0100878 """
879 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
880 Check that users has rights to use them and returs the admin_query
tierno701018c2019-06-25 11:13:14 +0000881 :param token_info: token_info rights obtained by token
tierno65ca36d2019-02-12 19:27:52 +0100882 :param kwargs: query string input.
883 :param method: http method: GET, POSST, PUT, ...
884 :param _id:
885 :return: admin_query dictionary with keys:
886 public: True, False or None
887 force: True or False
888 project_id: tuple with projects used for accessing an element
889 set_project: tuple with projects that a created element will belong to
890 method: show, list, delete, write
891 """
tierno701018c2019-06-25 11:13:14 +0000892 admin_query = {"force": False, "project_id": (token_info["project_id"], ), "username": token_info["username"],
delacruzramo029405d2019-09-26 10:52:56 +0200893 "admin": token_info["admin"], "public": None,
894 "allow_show_user_project_role": token_info["allow_show_user_project_role"]}
tierno65ca36d2019-02-12 19:27:52 +0100895 if kwargs:
896 # FORCE
897 if "FORCE" in kwargs:
898 if kwargs["FORCE"].lower() != "false": # if None or True set force to True
899 admin_query["force"] = True
900 del kwargs["FORCE"]
901 # PUBLIC
902 if "PUBLIC" in kwargs:
903 if kwargs["PUBLIC"].lower() != "false": # if None or True set public to True
904 admin_query["public"] = True
905 else:
906 admin_query["public"] = False
907 del kwargs["PUBLIC"]
908 # ADMIN
909 if "ADMIN" in kwargs:
910 behave_as = kwargs.pop("ADMIN")
911 if behave_as.lower() != "false":
tierno701018c2019-06-25 11:13:14 +0000912 if not token_info["admin"]:
tierno65ca36d2019-02-12 19:27:52 +0100913 raise NbiException("Only admin projects can use 'ADMIN' query string", HTTPStatus.UNAUTHORIZED)
914 if not behave_as or behave_as.lower() == "true": # convert True, None to empty list
915 admin_query["project_id"] = ()
916 elif isinstance(behave_as, (list, tuple)):
917 admin_query["project_id"] = behave_as
918 else: # isinstance(behave_as, str)
919 admin_query["project_id"] = (behave_as, )
920 if "SET_PROJECT" in kwargs:
921 set_project = kwargs.pop("SET_PROJECT")
922 if not set_project:
923 admin_query["set_project"] = list(admin_query["project_id"])
924 else:
925 if isinstance(set_project, str):
926 set_project = (set_project, )
927 if admin_query["project_id"]:
928 for p in set_project:
929 if p not in admin_query["project_id"]:
930 raise NbiException("Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
931 "'ADMIN='{p}'".format(p=p), HTTPStatus.UNAUTHORIZED)
932 admin_query["set_project"] = set_project
933
934 # PROJECT_READ
935 # if "PROJECT_READ" in kwargs:
936 # admin_query["project"] = kwargs.pop("project")
tierno701018c2019-06-25 11:13:14 +0000937 # if admin_query["project"] == token_info["project_id"]:
tierno65ca36d2019-02-12 19:27:52 +0100938 if method == "GET":
939 if _id:
940 admin_query["method"] = "show"
941 else:
942 admin_query["method"] = "list"
943 elif method == "DELETE":
944 admin_query["method"] = "delete"
945 else:
946 admin_query["method"] = "write"
947 return admin_query
948
tiernoc94c3df2018-02-09 15:38:54 +0100949 @cherrypy.expose
tiernob24258a2018-10-04 18:39:49 +0200950 def default(self, main_topic=None, version=None, topic=None, _id=None, item=None, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000951 token_info = None
tiernof27c79b2018-03-12 17:08:42 +0100952 outdata = None
953 _format = None
tierno0f98af52018-03-19 10:28:22 +0100954 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +0200955 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +0200956 rollback = []
tierno701018c2019-06-25 11:13:14 +0000957 engine_session = None
tiernoc94c3df2018-02-09 15:38:54 +0100958 try:
tiernob24258a2018-10-04 18:39:49 +0200959 if not main_topic or not version or not topic:
960 raise NbiException("URL must contain at least 'main_topic/version/topic'",
961 HTTPStatus.METHOD_NOT_ALLOWED)
vijay.r35ef2f72019-04-30 17:55:49 +0530962 if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm", "nspm"):
tiernob24258a2018-10-04 18:39:49 +0200963 raise NbiException("URL main_topic '{}' not supported".format(main_topic),
964 HTTPStatus.METHOD_NOT_ALLOWED)
tiernoc94c3df2018-02-09 15:38:54 +0100965 if version != 'v1':
966 raise NbiException("URL version '{}' not supported".format(version), HTTPStatus.METHOD_NOT_ALLOWED)
967
tiernof27c79b2018-03-12 17:08:42 +0100968 if kwargs and "METHOD" in kwargs and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
969 method = kwargs.pop("METHOD")
970 else:
971 method = cherrypy.request.method
tierno65ca36d2019-02-12 19:27:52 +0100972
tierno701018c2019-06-25 11:13:14 +0000973 role_permission = self._check_valid_url_method(method, main_topic, version, topic, _id, item, *args)
974 query_string_operations = self._extract_query_string_operations(kwargs, method)
tiernob24258a2018-10-04 18:39:49 +0200975 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +0100976 return self.token(method, _id, kwargs)
delacruzramo029405d2019-09-26 10:52:56 +0200977 token_info = self.authenticator.authorize(role_permission, query_string_operations, _id)
tierno701018c2019-06-25 11:13:14 +0000978 engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
tiernof27c79b2018-03-12 17:08:42 +0100979 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +0200980 engine_topic = topic
981 if topic == "subscriptions":
982 engine_topic = main_topic + "_" + topic
vijay.r35ef2f72019-04-30 17:55:49 +0530983 if item and topic != "pm_jobs":
tiernob24258a2018-10-04 18:39:49 +0200984 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +0100985
tiernob24258a2018-10-04 18:39:49 +0200986 if main_topic == "nsd":
987 engine_topic = "nsds"
988 elif main_topic == "vnfpkgm":
989 engine_topic = "vnfds"
delacruzramo271d2002019-12-02 21:00:37 +0100990 if topic == "vnfpkg_op_occs":
991 engine_topic = "vnfpkgops"
992 if topic == "vnf_packages" and item == "action":
993 engine_topic = "vnfpkgops"
tiernob24258a2018-10-04 18:39:49 +0200994 elif main_topic == "nslcm":
995 engine_topic = "nsrs"
996 if topic == "ns_lcm_op_occs":
997 engine_topic = "nslcmops"
998 if topic == "vnfrs" or topic == "vnf_instances":
999 engine_topic = "vnfrs"
garciadeblas9750c5a2018-10-15 16:20:35 +02001000 elif main_topic == "nst":
1001 engine_topic = "nsts"
1002 elif main_topic == "nsilcm":
1003 engine_topic = "nsis"
1004 if topic == "nsi_lcm_op_occs":
delacruzramoc061f562019-04-05 11:00:02 +02001005 engine_topic = "nsilcmops"
tiernob24258a2018-10-04 18:39:49 +02001006 elif main_topic == "pdu":
1007 engine_topic = "pdus"
tierno65ca36d2019-02-12 19:27:52 +01001008 if engine_topic == "vims": # TODO this is for backward compatibility, it will be removed in the future
tiernob24258a2018-10-04 18:39:49 +02001009 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +01001010
1011 if method == "GET":
Felipe Vicens07f31722018-10-29 15:16:44 +01001012 if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
garciadeblas9750c5a2018-10-15 16:20:35 +02001013 if item in ("vnfd", "nsd", "nst"):
tiernof27c79b2018-03-12 17:08:42 +01001014 path = "$DESCRIPTOR"
1015 elif args:
1016 path = args
tiernob24258a2018-10-04 18:39:49 +02001017 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +01001018 path = ()
1019 else:
1020 path = None
tierno701018c2019-06-25 11:13:14 +00001021 file, _format = self.engine.get_file(engine_session, engine_topic, _id, path,
tierno2236d202018-05-16 19:05:16 +02001022 cherrypy.request.headers.get("Accept"))
tiernof27c79b2018-03-12 17:08:42 +01001023 outdata = file
1024 elif not _id:
tierno701018c2019-06-25 11:13:14 +00001025 outdata = self.engine.get_item_list(engine_session, engine_topic, kwargs)
tiernoc94c3df2018-02-09 15:38:54 +01001026 else:
vijay.r35ef2f72019-04-30 17:55:49 +05301027 if item == "reports":
1028 # TODO check that project_id (_id in this context) has permissions
1029 _id = args[0]
tierno701018c2019-06-25 11:13:14 +00001030 outdata = self.engine.get_item(engine_session, engine_topic, _id)
delacruzramo271d2002019-12-02 21:00:37 +01001031
tiernof27c79b2018-03-12 17:08:42 +01001032 elif method == "POST":
tiernobdebce92019-07-01 15:36:49 +00001033 cherrypy.response.status = HTTPStatus.CREATED.value
garciadeblas9750c5a2018-10-15 16:20:35 +02001034 if topic in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"):
tiernof27c79b2018-03-12 17:08:42 +01001035 _id = cherrypy.request.headers.get("Transaction-Id")
1036 if not _id:
tiernobdebce92019-07-01 15:36:49 +00001037 _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, {}, None,
1038 cherrypy.request.headers)
tierno701018c2019-06-25 11:13:14 +00001039 completed = self.engine.upload_content(engine_session, engine_topic, _id, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +01001040 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +01001041 if completed:
tiernob24258a2018-10-04 18:39:49 +02001042 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001043 else:
1044 cherrypy.response.headers["Transaction-Id"] = _id
1045 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +02001046 elif topic == "ns_instances_content":
1047 # creates NSR
tiernobdebce92019-07-01 15:36:49 +00001048 _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs)
tiernob24258a2018-10-04 18:39:49 +02001049 # creates nslcmop
1050 indata["lcmOperationType"] = "instantiate"
1051 indata["nsInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001052 nslcmop_id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", indata, None)
tiernob24258a2018-10-04 18:39:49 +02001053 self._set_location_header(main_topic, version, topic, _id)
kuuse078f55e2019-05-16 19:24:21 +02001054 outdata = {"id": _id, "nslcmop_id": nslcmop_id}
tiernob24258a2018-10-04 18:39:49 +02001055 elif topic == "ns_instances" and item:
1056 indata["lcmOperationType"] = item
1057 indata["nsInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001058 _id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", indata, kwargs)
tiernob24258a2018-10-04 18:39:49 +02001059 self._set_location_header(main_topic, version, "ns_lcm_op_occs", _id)
tierno65acb4d2018-04-06 16:42:40 +02001060 outdata = {"id": _id}
1061 cherrypy.response.status = HTTPStatus.ACCEPTED.value
garciadeblas9750c5a2018-10-15 16:20:35 +02001062 elif topic == "netslice_instances_content":
Felipe Vicens07f31722018-10-29 15:16:44 +01001063 # creates NetSlice_Instance_record (NSIR)
tiernobdebce92019-07-01 15:36:49 +00001064 _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs)
Felipe Vicens07f31722018-10-29 15:16:44 +01001065 self._set_location_header(main_topic, version, topic, _id)
garciadeblas9750c5a2018-10-15 16:20:35 +02001066 indata["lcmOperationType"] = "instantiate"
Felipe Vicens126af572019-06-05 19:13:04 +02001067 indata["netsliceInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001068 nsilcmop_id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", indata, kwargs)
kuuse078f55e2019-05-16 19:24:21 +02001069 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
garciadeblas9750c5a2018-10-15 16:20:35 +02001070 elif topic == "netslice_instances" and item:
1071 indata["lcmOperationType"] = item
Felipe Vicens126af572019-06-05 19:13:04 +02001072 indata["netsliceInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001073 _id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", indata, kwargs)
garciadeblas9750c5a2018-10-15 16:20:35 +02001074 self._set_location_header(main_topic, version, "nsi_lcm_op_occs", _id)
1075 outdata = {"id": _id}
1076 cherrypy.response.status = HTTPStatus.ACCEPTED.value
delacruzramo271d2002019-12-02 21:00:37 +01001077 elif topic == "vnf_packages" and item == "action":
1078 indata["lcmOperationType"] = item
1079 indata["vnfPkgId"] = _id
1080 _id, _ = self.engine.new_item(rollback, engine_session, "vnfpkgops", indata, kwargs)
1081 self._set_location_header(main_topic, version, "vnfpkg_op_occs", _id)
1082 outdata = {"id": _id}
1083 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +01001084 else:
tiernobdebce92019-07-01 15:36:49 +00001085 _id, op_id = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs,
1086 cherrypy.request.headers)
tiernob24258a2018-10-04 18:39:49 +02001087 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001088 outdata = {"id": _id}
tiernobdebce92019-07-01 15:36:49 +00001089 if op_id:
1090 outdata["op_id"] = op_id
1091 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernob24258a2018-10-04 18:39:49 +02001092 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tierno09c073e2018-04-26 13:36:48 +02001093
tiernoc94c3df2018-02-09 15:38:54 +01001094 elif method == "DELETE":
1095 if not _id:
tierno701018c2019-06-25 11:13:14 +00001096 outdata = self.engine.del_item_list(engine_session, engine_topic, kwargs)
tierno09c073e2018-04-26 13:36:48 +02001097 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +01001098 else: # len(args) > 1
tiernoe8631782018-12-21 13:31:52 +00001099 delete_in_process = False
tierno701018c2019-06-25 11:13:14 +00001100 if topic == "ns_instances_content" and not engine_session["force"]:
tiernob24258a2018-10-04 18:39:49 +02001101 nslcmop_desc = {
1102 "lcmOperationType": "terminate",
1103 "nsInstanceId": _id,
1104 "autoremove": True
1105 }
tierno1c38f2f2020-03-24 11:51:39 +00001106 opp_id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", nslcmop_desc, kwargs)
tiernoe8631782018-12-21 13:31:52 +00001107 if opp_id:
1108 delete_in_process = True
1109 outdata = {"_id": opp_id}
1110 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tierno701018c2019-06-25 11:13:14 +00001111 elif topic == "netslice_instances_content" and not engine_session["force"]:
garciadeblas9750c5a2018-10-15 16:20:35 +02001112 nsilcmop_desc = {
1113 "lcmOperationType": "terminate",
Felipe Vicens126af572019-06-05 19:13:04 +02001114 "netsliceInstanceId": _id,
garciadeblas9750c5a2018-10-15 16:20:35 +02001115 "autoremove": True
1116 }
tiernobdebce92019-07-01 15:36:49 +00001117 opp_id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", nsilcmop_desc, None)
tiernoe8631782018-12-21 13:31:52 +00001118 if opp_id:
1119 delete_in_process = True
1120 outdata = {"_id": opp_id}
1121 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1122 if not delete_in_process:
tierno701018c2019-06-25 11:13:14 +00001123 self.engine.del_item(engine_session, engine_topic, _id)
tierno09c073e2018-04-26 13:36:48 +02001124 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
delacruzramofe598fe2019-10-23 18:25:11 +02001125 if engine_topic in ("vim_accounts", "wim_accounts", "sdns", "k8sclusters", "k8srepos"):
tierno09c073e2018-04-26 13:36:48 +02001126 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1127
tierno7ae10112018-05-18 14:36:02 +02001128 elif method in ("PUT", "PATCH"):
tiernobdebce92019-07-01 15:36:49 +00001129 op_id = None
tierno701018c2019-06-25 11:13:14 +00001130 if not indata and not kwargs and not engine_session.get("set_project"):
tiernoc94c3df2018-02-09 15:38:54 +01001131 raise NbiException("Nothing to update. Provide payload and/or query string",
1132 HTTPStatus.BAD_REQUEST)
garciadeblas9750c5a2018-10-15 16:20:35 +02001133 if item in ("nsd_content", "package_content", "nst_content") and method == "PUT":
tierno701018c2019-06-25 11:13:14 +00001134 completed = self.engine.upload_content(engine_session, engine_topic, _id, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +01001135 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +01001136 if not completed:
1137 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +01001138 else:
tiernobdebce92019-07-01 15:36:49 +00001139 op_id = self.engine.edit_item(engine_session, engine_topic, _id, indata, kwargs)
1140
1141 if op_id:
1142 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1143 outdata = {"op_id": op_id}
1144 else:
1145 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
1146 outdata = None
tiernoc94c3df2018-02-09 15:38:54 +01001147 else:
1148 raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tiernoa6bb45d2019-06-14 09:45:39 +00001149
1150 # if Role information changes, it is needed to reload the information of roles
1151 if topic == "roles" and method != "GET":
1152 self.authenticator.load_operation_to_allowed_roles()
delacruzramoad682a52019-12-10 16:26:34 +01001153
1154 if topic == "projects" and method == "DELETE" \
1155 or topic in ["users", "roles"] and method in ["PUT", "PATCH", "DELETE"]:
1156 self.authenticator.remove_token_from_cache()
1157
tierno701018c2019-06-25 11:13:14 +00001158 return self._format_out(outdata, token_info, _format)
tiernob24258a2018-10-04 18:39:49 +02001159 except Exception as e:
tierno36ec8602018-11-02 17:27:11 +01001160 if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException,
delacruzramo01b15d32019-07-02 14:37:47 +02001161 ValidationError, AuthconnException)):
tiernob24258a2018-10-04 18:39:49 +02001162 http_code_value = cherrypy.response.status = e.http_code.value
1163 http_code_name = e.http_code.name
1164 cherrypy.log("Exception {}".format(e))
1165 else:
1166 http_code_value = cherrypy.response.status = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Felipe Vicense36ab852018-11-23 14:12:09 +01001167 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
tiernob24258a2018-10-04 18:39:49 +02001168 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +02001169 if hasattr(outdata, "close"): # is an open file
1170 outdata.close()
tiernob24258a2018-10-04 18:39:49 +02001171 error_text = str(e)
1172 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +02001173 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +02001174 try:
tiernocc103432018-10-19 14:10:35 +02001175 if rollback_item.get("operation") == "set":
1176 self.engine.db.set_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
1177 rollback_item["content"], fail_on_empty=False)
1178 else:
tiernoe8631782018-12-21 13:31:52 +00001179 self.engine.db.del_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
1180 fail_on_empty=False)
tierno3ace63c2018-05-03 17:51:43 +02001181 except Exception as e2:
tiernob24258a2018-10-04 18:39:49 +02001182 rollback_error_text = "Rollback Exception {}: {}".format(rollback_item, e2)
1183 cherrypy.log(rollback_error_text)
1184 error_text += ". " + rollback_error_text
1185 # if isinstance(e, MsgException):
1186 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1187 # engine_topic[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +01001188 problem_details = {
tiernob24258a2018-10-04 18:39:49 +02001189 "code": http_code_name,
1190 "status": http_code_value,
1191 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +01001192 }
tierno701018c2019-06-25 11:13:14 +00001193 return self._format_out(problem_details, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001194 # raise cherrypy.HTTPError(e.http_code.value, str(e))
tiernoa5035702019-07-29 08:54:42 +00001195 finally:
1196 if token_info:
1197 self._format_login(token_info)
1198 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
1199 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1200 if outdata.get(logging_id):
1201 cherrypy.request.login += ";{}={}".format(logging_id, outdata[logging_id][:36])
tiernoc94c3df2018-02-09 15:38:54 +01001202
1203
tiernoc94c3df2018-02-09 15:38:54 +01001204def _start_service():
1205 """
1206 Callback function called when cherrypy.engine starts
1207 Override configuration with env variables
1208 Set database, storage, message configuration
1209 Init database with admin/admin user password
1210 """
tierno932499c2019-01-28 17:28:10 +00001211 global nbi_server
1212 global subscription_thread
tiernoc94c3df2018-02-09 15:38:54 +01001213 cherrypy.log.error("Starting osm_nbi")
1214 # update general cherrypy configuration
1215 update_dict = {}
1216
1217 engine_config = cherrypy.tree.apps['/osm'].config
1218 for k, v in environ.items():
1219 if not k.startswith("OSMNBI_"):
1220 continue
tiernoe1281182018-05-22 12:24:36 +02001221 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +01001222 if not k2:
1223 continue
1224 try:
1225 # update static configuration
1226 if k == 'OSMNBI_STATIC_DIR':
1227 engine_config["/static"]['tools.staticdir.dir'] = v
1228 engine_config["/static"]['tools.staticdir.on'] = True
1229 elif k == 'OSMNBI_SOCKET_PORT' or k == 'OSMNBI_SERVER_PORT':
1230 update_dict['server.socket_port'] = int(v)
1231 elif k == 'OSMNBI_SOCKET_HOST' or k == 'OSMNBI_SERVER_HOST':
1232 update_dict['server.socket_host'] = v
tiernof5298be2018-05-16 14:43:57 +02001233 elif k1 in ("server", "test", "auth", "log"):
1234 update_dict[k1 + '.' + k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001235 elif k1 in ("message", "database", "storage", "authentication"):
tiernof5298be2018-05-16 14:43:57 +02001236 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001237 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +01001238 engine_config[k1][k2] = int(v)
1239 else:
1240 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001241
tiernoc94c3df2018-02-09 15:38:54 +01001242 except ValueError as e:
1243 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1244 except Exception as e:
1245 cherrypy.log.warn("skipping environ '{}' on exception '{}'".format(k, e))
1246
1247 if update_dict:
1248 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +02001249 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001250
1251 # logging cherrypy
1252 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1253 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
1254 logger_server = logging.getLogger("cherrypy.error")
1255 logger_access = logging.getLogger("cherrypy.access")
1256 logger_cherry = logging.getLogger("cherrypy")
1257 logger_nbi = logging.getLogger("nbi")
1258
tiernof5298be2018-05-16 14:43:57 +02001259 if "log.file" in engine_config["global"]:
1260 file_handler = logging.handlers.RotatingFileHandler(engine_config["global"]["log.file"],
tiernoc94c3df2018-02-09 15:38:54 +01001261 maxBytes=100e6, backupCount=9, delay=0)
1262 file_handler.setFormatter(log_formatter_simple)
1263 logger_cherry.addHandler(file_handler)
1264 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +02001265 # log always to standard output
1266 for format_, logger in {"nbi.server %(filename)s:%(lineno)s": logger_server,
1267 "nbi.access %(filename)s:%(lineno)s": logger_access,
1268 "%(name)s %(filename)s:%(lineno)s": logger_nbi
1269 }.items():
1270 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
1271 log_formatter_cherry = logging.Formatter(log_format_cherry, datefmt='%Y-%m-%dT%H:%M:%S')
1272 str_handler = logging.StreamHandler()
1273 str_handler.setFormatter(log_formatter_cherry)
1274 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +01001275
tiernof5298be2018-05-16 14:43:57 +02001276 if engine_config["global"].get("log.level"):
1277 logger_cherry.setLevel(engine_config["global"]["log.level"])
1278 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +01001279
1280 # logging other modules
1281 for k1, logname in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
1282 engine_config[k1]["logger_name"] = logname
1283 logger_module = logging.getLogger(logname)
1284 if "logfile" in engine_config[k1]:
1285 file_handler = logging.handlers.RotatingFileHandler(engine_config[k1]["logfile"],
tierno2236d202018-05-16 19:05:16 +02001286 maxBytes=100e6, backupCount=9, delay=0)
tiernoc94c3df2018-02-09 15:38:54 +01001287 file_handler.setFormatter(log_formatter_simple)
1288 logger_module.addHandler(file_handler)
1289 if "loglevel" in engine_config[k1]:
1290 logger_module.setLevel(engine_config[k1]["loglevel"])
1291 # TODO add more entries, e.g.: storage
1292 cherrypy.tree.apps['/osm'].root.engine.start(engine_config)
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001293 cherrypy.tree.apps['/osm'].root.authenticator.start(engine_config)
tiernod985a8d2018-10-19 14:12:28 +02001294 cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version)
1295 cherrypy.tree.apps['/osm'].root.authenticator.init_db(target_version=auth_database_version)
tiernobee508e2019-01-21 11:21:49 +00001296
tierno932499c2019-01-28 17:28:10 +00001297 # start subscriptions thread:
1298 subscription_thread = SubscriptionThread(config=engine_config, engine=nbi_server.engine)
1299 subscription_thread.start()
1300 # Do not capture except SubscriptionException
1301
tiernob2e48bd2020-02-04 15:47:18 +00001302 backend = engine_config["authentication"]["backend"]
1303 cherrypy.log.error("Starting OSM NBI Version '{} {}' with '{}' authentication backend"
1304 .format(nbi_version, nbi_version_date, backend))
tiernoc94c3df2018-02-09 15:38:54 +01001305
1306
1307def _stop_service():
1308 """
1309 Callback function called when cherrypy.engine stops
1310 TODO: Ending database connections.
1311 """
tierno932499c2019-01-28 17:28:10 +00001312 global subscription_thread
tierno65ca36d2019-02-12 19:27:52 +01001313 if subscription_thread:
1314 subscription_thread.terminate()
tierno932499c2019-01-28 17:28:10 +00001315 subscription_thread = None
tiernoc94c3df2018-02-09 15:38:54 +01001316 cherrypy.tree.apps['/osm'].root.engine.stop()
1317 cherrypy.log.error("Stopping osm_nbi")
1318
tierno2236d202018-05-16 19:05:16 +02001319
tiernof5298be2018-05-16 14:43:57 +02001320def nbi(config_file):
tierno932499c2019-01-28 17:28:10 +00001321 global nbi_server
tiernoc94c3df2018-02-09 15:38:54 +01001322 # conf = {
1323 # '/': {
1324 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1325 # 'tools.sessions.on': True,
1326 # 'tools.response_headers.on': True,
1327 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1328 # }
1329 # }
1330 # cherrypy.Server.ssl_module = 'builtin'
1331 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1332 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1333 # cherrypy.Server.thread_pool = 10
1334 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1335
1336 # cherrypy.config.update({'tools.auth_basic.on': True,
1337 # 'tools.auth_basic.realm': 'localhost',
1338 # 'tools.auth_basic.checkpassword': validate_password})
tierno932499c2019-01-28 17:28:10 +00001339 nbi_server = Server()
tiernoc94c3df2018-02-09 15:38:54 +01001340 cherrypy.engine.subscribe('start', _start_service)
1341 cherrypy.engine.subscribe('stop', _stop_service)
tierno932499c2019-01-28 17:28:10 +00001342 cherrypy.quickstart(nbi_server, '/osm', config_file)
tiernof5298be2018-05-16 14:43:57 +02001343
1344
1345def usage():
1346 print("""Usage: {} [options]
1347 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1348 -h|--help: shows this help
1349 """.format(sys.argv[0]))
tierno2236d202018-05-16 19:05:16 +02001350 # --log-socket-host HOST: send logs to this host")
1351 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +01001352
1353
1354if __name__ == '__main__':
tiernof5298be2018-05-16 14:43:57 +02001355 try:
1356 # load parameters and configuration
1357 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
1358 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1359 config_file = None
1360 for o, a in opts:
1361 if o in ("-h", "--help"):
1362 usage()
1363 sys.exit()
1364 elif o in ("-c", "--config"):
1365 config_file = a
1366 # elif o == "--log-socket-port":
1367 # log_socket_port = a
1368 # elif o == "--log-socket-host":
1369 # log_socket_host = a
1370 # elif o == "--log-file":
1371 # log_file = a
1372 else:
1373 assert False, "Unhandled option"
1374 if config_file:
1375 if not path.isfile(config_file):
1376 print("configuration file '{}' that not exist".format(config_file), file=sys.stderr)
1377 exit(1)
1378 else:
1379 for config_file in (__file__[:__file__.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
1380 if path.isfile(config_file):
1381 break
1382 else:
1383 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys.stderr)
1384 exit(1)
1385 nbi(config_file)
1386 except getopt.GetoptError as e:
1387 print(str(e), file=sys.stderr)
1388 # usage()
1389 exit(1)