blob: 1085ebc6e1fefa51e2dde66d35a1517df8385a15 [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
tierno9c630112019-08-29 14:21:41 +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
49
tiernoc94c3df2018-02-09 15:38:54 +010050
51"""
tiernof27c79b2018-03-12 17:08:42 +010052North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
tiernoc94c3df2018-02-09 15:38:54 +010053URL: /osm GET POST PUT DELETE PATCH
garciadeblas9750c5a2018-10-15 16:20:35 +020054 /nsd/v1
tierno2236d202018-05-16 19:05:16 +020055 /ns_descriptors_content O O
56 /<nsdInfoId> O O O O
tiernoc94c3df2018-02-09 15:38:54 +010057 /ns_descriptors O5 O5
58 /<nsdInfoId> O5 O5 5
59 /nsd_content O5 O5
tiernof27c79b2018-03-12 17:08:42 +010060 /nsd O
61 /artifacts[/<artifactPath>] O
tiernoc94c3df2018-02-09 15:38:54 +010062 /pnf_descriptors 5 5
63 /<pnfdInfoId> 5 5 5
64 /pnfd_content 5 5
tiernof27c79b2018-03-12 17:08:42 +010065 /subscriptions 5 5
66 /<subscriptionId> 5 X
tiernoc94c3df2018-02-09 15:38:54 +010067
68 /vnfpkgm/v1
tierno55945e72018-04-06 16:40:27 +020069 /vnf_packages_content O O
tierno2236d202018-05-16 19:05:16 +020070 /<vnfPkgId> O O
tiernoc94c3df2018-02-09 15:38:54 +010071 /vnf_packages O5 O5
72 /<vnfPkgId> O5 O5 5
tiernoc94c3df2018-02-09 15:38:54 +010073 /package_content O5 O5
74 /upload_from_uri X
tiernof27c79b2018-03-12 17:08:42 +010075 /vnfd O5
76 /artifacts[/<artifactPath>] O5
77 /subscriptions X X
78 /<subscriptionId> X X
tiernoc94c3df2018-02-09 15:38:54 +010079
80 /nslcm/v1
tiernof27c79b2018-03-12 17:08:42 +010081 /ns_instances_content O O
tierno2236d202018-05-16 19:05:16 +020082 /<nsInstanceId> O O
tiernof27c79b2018-03-12 17:08:42 +010083 /ns_instances 5 5
tierno95692442018-05-24 18:05:28 +020084 /<nsInstanceId> O5 O5
tierno65acb4d2018-04-06 16:42:40 +020085 instantiate O5
86 terminate O5
87 action O
88 scale O5
89 heal 5
tiernoc94c3df2018-02-09 15:38:54 +010090 /ns_lcm_op_occs 5 5
91 /<nsLcmOpOccId> 5 5 5
92 TO BE COMPLETED 5 5
tiernof759d822018-06-11 18:54:54 +020093 /vnf_instances (also vnfrs for compatibility) O
94 /<vnfInstanceId> O
tiernof27c79b2018-03-12 17:08:42 +010095 /subscriptions 5 5
96 /<subscriptionId> 5 X
garciadeblas9750c5a2018-10-15 16:20:35 +020097
tiernocb83c942018-09-24 17:28:13 +020098 /pdu/v1
tierno032916c2019-03-22 13:27:12 +000099 /pdu_descriptors O O
tiernocb83c942018-09-24 17:28:13 +0200100 /<id> O O O O
garciadeblas9750c5a2018-10-15 16:20:35 +0200101
tiernof27c79b2018-03-12 17:08:42 +0100102 /admin/v1
103 /tokens O O
tierno2236d202018-05-16 19:05:16 +0200104 /<id> O O
tiernof27c79b2018-03-12 17:08:42 +0100105 /users O O
tiernocd54a4a2018-09-12 16:40:35 +0200106 /<id> O O O O
tiernof27c79b2018-03-12 17:08:42 +0100107 /projects O O
tierno2236d202018-05-16 19:05:16 +0200108 /<id> O O
tierno55ba2e62018-12-11 17:22:22 +0000109 /vim_accounts (also vims for compatibility) O O
110 /<id> O O O
111 /wim_accounts O O
tierno2236d202018-05-16 19:05:16 +0200112 /<id> O O O
tierno0f98af52018-03-19 10:28:22 +0100113 /sdns O O
tierno2236d202018-05-16 19:05:16 +0200114 /<id> O O O
tiernoc94c3df2018-02-09 15:38:54 +0100115
garciadeblas9750c5a2018-10-15 16:20:35 +0200116 /nst/v1 O O
117 /netslice_templates_content O O
118 /<nstInfoId> O O O O
119 /netslice_templates O O
120 /<nstInfoId> O O O
121 /nst_content O O
122 /nst O
123 /artifacts[/<artifactPath>] O
124 /subscriptions X X
125 /<subscriptionId> X X
126
127 /nsilcm/v1
128 /netslice_instances_content O O
129 /<SliceInstanceId> O O
130 /netslice_instances O O
131 /<SliceInstanceId> O O
132 instantiate O
133 terminate O
134 action O
135 /nsi_lcm_op_occs O O
136 /<nsiLcmOpOccId> O O O
137 /subscriptions X X
138 /<subscriptionId> X X
139
tierno2236d202018-05-16 19:05:16 +0200140query string:
141 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
tierno36ec8602018-11-02 17:27:11 +0100142 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
143 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
144 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
145 attrName := string
tierno2236d202018-05-16 19:05:16 +0200146 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
147 item of the array, that is, pass if any item of the array pass the filter.
148 It allows both ne and neq for not equal
149 TODO: 4.3.3 Attribute selectors
150 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
tiernoc94c3df2018-02-09 15:38:54 +0100151 (none) … same as “exclude_default”
152 all_fields … all attributes.
tierno2236d202018-05-16 19:05:16 +0200153 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
154 conditionally mandatory, and that are not provided in <list>.
155 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
156 are not conditionally mandatory, and that are provided in <list>.
157 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
158 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
159 the particular resource
160 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
161 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
162 present specification for the particular resource, but that are not part of <list>
tierno65ca36d2019-02-12 19:27:52 +0100163 Additionally it admits some administrator values:
164 FORCE: To force operations skipping dependency checkings
165 ADMIN: To act as an administrator or a different project
166 PUBLIC: To get public descriptors or set a descriptor as public
167 SET_PROJECT: To make a descriptor available for other project
168
tiernoc94c3df2018-02-09 15:38:54 +0100169Header field name Reference Example Descriptions
170 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
171 This header field shall be present if the response is expected to have a non-empty message body.
172 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
173 This header field shall be present if the request has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200174 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
175 Details are specified in clause 4.5.3.
tiernoc94c3df2018-02-09 15:38:54 +0100176 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
177Header field name Reference Example Descriptions
178 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
179 This header field shall be present if the response has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200180 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
181 new resource has been created.
tiernoc94c3df2018-02-09 15:38:54 +0100182 This header field shall be present if the response status code is 201 or 3xx.
tierno2236d202018-05-16 19:05:16 +0200183 In the present document this header field is also used if the response status code is 202 and a new resource was
184 created.
185 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
186 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
187 token.
188 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
189 certain resources.
190 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
191 response, and the total length of the file.
tiernoc94c3df2018-02-09 15:38:54 +0100192 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
tiernoc94c3df2018-02-09 15:38:54 +0100193"""
194
tierno701018c2019-06-25 11:13:14 +0000195valid_query_string = ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
196# ^ Contains possible administrative query string words:
197# ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
198# (not owned by my session project).
199# PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
200# FORCE=True(by default)|False: Force edition/deletion operations
201# SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
202
203valid_url_methods = {
204 # contains allowed URL and methods, and the role_permission name
205 "admin": {
206 "v1": {
207 "tokens": {"METHODS": ("GET", "POST", "DELETE"),
208 "ROLE_PERMISSION": "tokens:",
209 "<ID>": {"METHODS": ("GET", "DELETE"),
210 "ROLE_PERMISSION": "tokens:id:"
211 }
212 },
213 "users": {"METHODS": ("GET", "POST"),
214 "ROLE_PERMISSION": "users:",
215 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
216 "ROLE_PERMISSION": "users:id:"
217 }
218 },
219 "projects": {"METHODS": ("GET", "POST"),
220 "ROLE_PERMISSION": "projects:",
221 "<ID>": {"METHODS": ("GET", "DELETE", "PUT"),
222 "ROLE_PERMISSION": "projects:id:"}
223 },
224 "roles": {"METHODS": ("GET", "POST"),
225 "ROLE_PERMISSION": "roles:",
226 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PUT"),
227 "ROLE_PERMISSION": "roles:id:"
228 }
229 },
230 "vims": {"METHODS": ("GET", "POST"),
231 "ROLE_PERMISSION": "vims:",
232 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
233 "ROLE_PERMISSION": "vims:id:"
234 }
235 },
236 "vim_accounts": {"METHODS": ("GET", "POST"),
237 "ROLE_PERMISSION": "vim_accounts:",
238 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
239 "ROLE_PERMISSION": "vim_accounts:id:"
240 }
241 },
242 "wim_accounts": {"METHODS": ("GET", "POST"),
243 "ROLE_PERMISSION": "wim_accounts:",
244 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
245 "ROLE_PERMISSION": "wim_accounts:id:"
246 }
247 },
248 "sdns": {"METHODS": ("GET", "POST"),
249 "ROLE_PERMISSION": "sdn_controllers:",
250 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
251 "ROLE_PERMISSION": "sdn_controllers:id:"
252 }
253 },
254 }
255 },
256 "pdu": {
257 "v1": {
258 "pdu_descriptors": {"METHODS": ("GET", "POST"),
259 "ROLE_PERMISSION": "pduds:",
260 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
261 "ROLE_PERMISSION": "pduds:id:"
262 }
263 },
264 }
265 },
266 "nsd": {
267 "v1": {
268 "ns_descriptors_content": {"METHODS": ("GET", "POST"),
269 "ROLE_PERMISSION": "nsds:",
270 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
271 "ROLE_PERMISSION": "nsds:id:"
272 }
273 },
274 "ns_descriptors": {"METHODS": ("GET", "POST"),
275 "ROLE_PERMISSION": "nsds:",
276 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
277 "ROLE_PERMISSION": "nsds:id:",
278 "nsd_content": {"METHODS": ("GET", "PUT"),
279 "ROLE_PERMISSION": "nsds:id:content:",
280 },
281 "nsd": {"METHODS": ("GET",), # descriptor inside package
282 "ROLE_PERMISSION": "nsds:id:content:"
283 },
284 "artifacts": {"*": {"METHODS": ("GET",),
285 "ROLE_PERMISSION": "nsds:id:nsd_artifact:"
286 }
287 }
288 }
289 },
290 "pnf_descriptors": {"TODO": ("GET", "POST"),
291 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
292 "pnfd_content": {"TODO": ("GET", "PUT")}
293 }
294 },
295 "subscriptions": {"TODO": ("GET", "POST"),
296 "<ID>": {"TODO": ("GET", "DELETE")}
297 },
298 }
299 },
300 "vnfpkgm": {
301 "v1": {
302 "vnf_packages_content": {"METHODS": ("GET", "POST"),
303 "ROLE_PERMISSION": "vnfds:",
304 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
305 "ROLE_PERMISSION": "vnfds:id:"}
306 },
307 "vnf_packages": {"METHODS": ("GET", "POST"),
308 "ROLE_PERMISSION": "vnfds:",
309 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
310 "ROLE_PERMISSION": "vnfds:id:",
311 "package_content": {"METHODS": ("GET", "PUT"), # package
312 "ROLE_PERMISSION": "vnfds:id:",
313 "upload_from_uri": {"METHODS": (),
314 "TODO": ("POST", ),
315 "ROLE_PERMISSION": "vnfds:id:upload:"
316 }
317 },
318 "vnfd": {"METHODS": ("GET", ), # descriptor inside package
319 "ROLE_PERMISSION": "vnfds:id:content:"
320 },
321 "artifacts": {"*": {"METHODS": ("GET", ),
322 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:"
323 }
324 }
325 }
326 },
327 "subscriptions": {"TODO": ("GET", "POST"),
328 "<ID>": {"TODO": ("GET", "DELETE")}
329 },
330 }
331 },
332 "nslcm": {
333 "v1": {
334 "ns_instances_content": {"METHODS": ("GET", "POST"),
335 "ROLE_PERMISSION": "ns_instances:",
336 "<ID>": {"METHODS": ("GET", "DELETE"),
337 "ROLE_PERMISSION": "ns_instances:id:"
338 }
339 },
340 "ns_instances": {"METHODS": ("GET", "POST"),
341 "ROLE_PERMISSION": "ns_instances:",
342 "<ID>": {"METHODS": ("GET", "DELETE"),
343 "ROLE_PERMISSION": "ns_instances:id:",
344 "scale": {"METHODS": ("POST",),
345 "ROLE_PERMISSION": "ns_instances:id:scale:"
346 },
347 "terminate": {"METHODS": ("POST",),
348 "ROLE_PERMISSION": "ns_instances:id:terminate:"
349 },
350 "instantiate": {"METHODS": ("POST",),
351 "ROLE_PERMISSION": "ns_instances:id:instantiate:"
352 },
353 "action": {"METHODS": ("POST",),
354 "ROLE_PERMISSION": "ns_instances:id:action:"
355 },
356 }
357 },
358 "ns_lcm_op_occs": {"METHODS": ("GET",),
359 "ROLE_PERMISSION": "ns_instances:opps:",
360 "<ID>": {"METHODS": ("GET",),
361 "ROLE_PERMISSION": "ns_instances:opps:id:"
362 },
363 },
364 "vnfrs": {"METHODS": ("GET",),
365 "ROLE_PERMISSION": "vnf_instances:",
366 "<ID>": {"METHODS": ("GET",),
367 "ROLE_PERMISSION": "vnf_instances:id:"
368 }
369 },
370 "vnf_instances": {"METHODS": ("GET",),
371 "ROLE_PERMISSION": "vnf_instances:",
372 "<ID>": {"METHODS": ("GET",),
373 "ROLE_PERMISSION": "vnf_instances:id:"
374 }
375 },
376 }
377 },
378 "nst": {
379 "v1": {
380 "netslice_templates_content": {"METHODS": ("GET", "POST"),
381 "ROLE_PERMISSION": "slice_templates:",
382 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
383 "ROLE_PERMISSION": "slice_templates:id:", }
384 },
385 "netslice_templates": {"METHODS": ("GET", "POST"),
386 "ROLE_PERMISSION": "slice_templates:",
387 "<ID>": {"METHODS": ("GET", "DELETE"),
388 "TODO": ("PATCH",),
389 "ROLE_PERMISSION": "slice_templates:id:",
390 "nst_content": {"METHODS": ("GET", "PUT"),
391 "ROLE_PERMISSION": "slice_templates:id:content:"
392 },
393 "nst": {"METHODS": ("GET",), # descriptor inside package
394 "ROLE_PERMISSION": "slice_templates:id:content:"
395 },
396 "artifacts": {"*": {"METHODS": ("GET",),
397 "ROLE_PERMISSION": "slice_templates:id:content:"
398 }
399 }
400 }
401 },
402 "subscriptions": {"TODO": ("GET", "POST"),
403 "<ID>": {"TODO": ("GET", "DELETE")}
404 },
405 }
406 },
407 "nsilcm": {
408 "v1": {
409 "netslice_instances_content": {"METHODS": ("GET", "POST"),
410 "ROLE_PERMISSION": "slice_instances:",
411 "<ID>": {"METHODS": ("GET", "DELETE"),
412 "ROLE_PERMISSION": "slice_instances:id:"
413 }
414 },
415 "netslice_instances": {"METHODS": ("GET", "POST"),
416 "ROLE_PERMISSION": "slice_instances:",
417 "<ID>": {"METHODS": ("GET", "DELETE"),
418 "ROLE_PERMISSION": "slice_instances:id:",
419 "terminate": {"METHODS": ("POST",),
420 "ROLE_PERMISSION": "slice_instances:id:terminate:"
421 },
422 "instantiate": {"METHODS": ("POST",),
423 "ROLE_PERMISSION": "slice_instances:id:instantiate:"
424 },
425 "action": {"METHODS": ("POST",),
426 "ROLE_PERMISSION": "slice_instances:id:action:"
427 },
428 }
429 },
430 "nsi_lcm_op_occs": {"METHODS": ("GET",),
431 "ROLE_PERMISSION": "slice_instances:opps:",
432 "<ID>": {"METHODS": ("GET",),
433 "ROLE_PERMISSION": "slice_instances:opps:id:",
434 },
435 },
436 }
437 },
438 "nspm": {
439 "v1": {
440 "pm_jobs": {
441 "<ID>": {
442 "reports": {
443 "<ID>": {"METHODS": ("GET",),
444 "ROLE_PERMISSION": "reports:id:",
445 }
446 }
447 },
448 },
449 },
450 },
451}
452
tiernoc94c3df2018-02-09 15:38:54 +0100453
454class NbiException(Exception):
455
456 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
457 Exception.__init__(self, message)
458 self.http_code = http_code
459
460
461class Server(object):
462 instance = 0
463 # to decode bytes to str
464 reader = getreader("utf-8")
465
466 def __init__(self):
467 self.instance += 1
468 self.engine = Engine()
tierno701018c2019-06-25 11:13:14 +0000469 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
tiernoc94c3df2018-02-09 15:38:54 +0100470
tiernoc94c3df2018-02-09 15:38:54 +0100471 def _format_in(self, kwargs):
472 try:
473 indata = None
474 if cherrypy.request.body.length:
475 error_text = "Invalid input format "
476
477 if "Content-Type" in cherrypy.request.headers:
478 if "application/json" in cherrypy.request.headers["Content-Type"]:
479 error_text = "Invalid json format "
480 indata = json.load(self.reader(cherrypy.request.body))
gcalvinode4adfe2018-10-30 11:46:09 +0100481 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100482 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
483 error_text = "Invalid yaml format "
484 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100485 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100486 elif "application/binary" in cherrypy.request.headers["Content-Type"] or \
487 "application/gzip" in cherrypy.request.headers["Content-Type"] or \
tiernof27c79b2018-03-12 17:08:42 +0100488 "application/zip" in cherrypy.request.headers["Content-Type"] or \
489 "text/plain" in cherrypy.request.headers["Content-Type"]:
490 indata = cherrypy.request.body # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100491 elif "multipart/form-data" in cherrypy.request.headers["Content-Type"]:
492 if "descriptor_file" in kwargs:
493 filecontent = kwargs.pop("descriptor_file")
494 if not filecontent.file:
495 raise NbiException("empty file or content", HTTPStatus.BAD_REQUEST)
tiernof27c79b2018-03-12 17:08:42 +0100496 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100497 if filecontent.content_type.value:
498 cherrypy.request.headers["Content-Type"] = filecontent.content_type.value
499 else:
500 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
501 # "Only 'Content-Type' of type 'application/json' or
502 # 'application/yaml' for input format are available")
503 error_text = "Invalid yaml format "
504 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100505 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100506 else:
507 error_text = "Invalid yaml format "
508 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100509 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100510 if not indata:
511 indata = {}
512
tiernoc94c3df2018-02-09 15:38:54 +0100513 format_yaml = False
514 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
515 format_yaml = True
516
517 for k, v in kwargs.items():
518 if isinstance(v, str):
519 if v == "":
520 kwargs[k] = None
521 elif format_yaml:
522 try:
523 kwargs[k] = yaml.load(v)
tiernoe1281182018-05-22 12:24:36 +0200524 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100525 pass
526 elif k.endswith(".gt") or k.endswith(".lt") or k.endswith(".gte") or k.endswith(".lte"):
527 try:
528 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200529 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100530 try:
531 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200532 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100533 pass
534 elif v.find(",") > 0:
535 kwargs[k] = v.split(",")
536 elif isinstance(v, (list, tuple)):
537 for index in range(0, len(v)):
538 if v[index] == "":
539 v[index] = None
540 elif format_yaml:
541 try:
542 v[index] = yaml.load(v[index])
tiernoe1281182018-05-22 12:24:36 +0200543 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100544 pass
545
tiernof27c79b2018-03-12 17:08:42 +0100546 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100547 except (ValueError, yaml.YAMLError) as exc:
548 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
549 except KeyError as exc:
550 raise NbiException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
tiernob92094f2018-05-11 13:44:22 +0200551 except Exception as exc:
552 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100553
554 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000555 def _format_out(data, token_info=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100556 """
557 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100558 :param data: response to be sent. Can be a dict, text or file
tierno701018c2019-06-25 11:13:14 +0000559 :param token_info: Contains among other username and project
tiernof27c79b2018-03-12 17:08:42 +0100560 :param _format: The format to be set as Content-Type ir data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100561 :return: None
562 """
tierno0f98af52018-03-19 10:28:22 +0100563 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100564 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100565 if accept and "text/html" in accept:
tierno701018c2019-06-25 11:13:14 +0000566 return html.format(data, cherrypy.request, cherrypy.response, token_info)
tierno09c073e2018-04-26 13:36:48 +0200567 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100568 return
569 elif hasattr(data, "read"): # file object
570 if _format:
571 cherrypy.response.headers["Content-Type"] = _format
572 elif "b" in data.mode: # binariy asssumig zip
573 cherrypy.response.headers["Content-Type"] = 'application/zip'
574 else:
575 cherrypy.response.headers["Content-Type"] = 'text/plain'
576 # TODO check that cherrypy close file. If not implement pending things to close per thread next
577 return data
tierno0f98af52018-03-19 10:28:22 +0100578 if accept:
tiernoc94c3df2018-02-09 15:38:54 +0100579 if "application/json" in accept:
580 cherrypy.response.headers["Content-Type"] = 'application/json; charset=utf-8'
581 a = json.dumps(data, indent=4) + "\n"
582 return a.encode("utf8")
583 elif "text/html" in accept:
tierno701018c2019-06-25 11:13:14 +0000584 return html.format(data, cherrypy.request, cherrypy.response, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100585
tiernof27c79b2018-03-12 17:08:42 +0100586 elif "application/yaml" in accept or "*/*" in accept or "text/plain" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100587 pass
tiernof717cbe2018-12-03 16:35:42 +0000588 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
589 elif cherrypy.response.status >= 400:
tiernoc94c3df2018-02-09 15:38:54 +0100590 raise cherrypy.HTTPError(HTTPStatus.NOT_ACCEPTABLE.value,
591 "Only 'Accept' of type 'application/json' or 'application/yaml' "
592 "for output format are available")
593 cherrypy.response.headers["Content-Type"] = 'application/yaml'
594 return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False,
595 encoding='utf-8', allow_unicode=True) # , canonical=True, default_style='"'
596
597 @cherrypy.expose
598 def index(self, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000599 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100600 try:
601 if cherrypy.request.method == "GET":
tierno701018c2019-06-25 11:13:14 +0000602 token_info = self.authenticator.authorize()
delacruzramo01b15d32019-07-02 14:37:47 +0200603 outdata = token_info # Home page
tiernoc94c3df2018-02-09 15:38:54 +0100604 else:
605 raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value,
tierno2236d202018-05-16 19:05:16 +0200606 "Method {} not allowed for tokens".format(cherrypy.request.method))
tiernoc94c3df2018-02-09 15:38:54 +0100607
tierno701018c2019-06-25 11:13:14 +0000608 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100609
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100610 except (EngineException, AuthException) as e:
tierno701018c2019-06-25 11:13:14 +0000611 # cherrypy.log("index Exception {}".format(e))
tiernoc94c3df2018-02-09 15:38:54 +0100612 cherrypy.response.status = e.http_code.value
tierno701018c2019-06-25 11:13:14 +0000613 return self._format_out("Welcome to OSM!", token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100614
615 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200616 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200617 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200618 try:
619 if cherrypy.request.method != "GET":
620 raise NbiException("Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED)
621 elif args or kwargs:
622 raise NbiException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED)
tierno9c630112019-08-29 14:21:41 +0000623 # TODO include version of other modules, pick up from some kafka admin message
624 return "<pre>NBI:\n version: {}\n date: {}\n".format(nbi_version, nbi_version_date)
tierno55945e72018-04-06 16:40:27 +0200625 except NbiException as e:
626 cherrypy.response.status = e.http_code.value
627 problem_details = {
628 "code": e.http_code.name,
629 "status": e.http_code.value,
630 "detail": str(e),
631 }
632 return self._format_out(problem_details, None)
633
tiernoa5035702019-07-29 08:54:42 +0000634 @staticmethod
635 def _format_login(token_info):
636 """
637 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
638 log this information
639 :param token_info: Dictionary with token content
640 :return: None
641 """
642 cherrypy.request.login = token_info.get("username", "-")
643 if token_info.get("project_name"):
644 cherrypy.request.login += "/" + token_info["project_name"]
645 if token_info.get("id"):
646 cherrypy.request.login += ";session=" + token_info["id"][0:12]
647
tierno55945e72018-04-06 16:40:27 +0200648 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100649 def token(self, method, token_id=None, kwargs=None):
tierno701018c2019-06-25 11:13:14 +0000650 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100651 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +0100652 indata = self._format_in(kwargs)
653 if not isinstance(indata, dict):
654 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST)
tiernoa5035702019-07-29 08:54:42 +0000655
656 if method == "GET":
657 token_info = self.authenticator.authorize()
658 # for logging
659 self._format_login(token_info)
660 if token_id:
661 outdata = self.authenticator.get_token(token_info, token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100662 else:
tiernoa5035702019-07-29 08:54:42 +0000663 outdata = self.authenticator.get_token_list(token_info)
664 elif method == "POST":
665 try:
666 token_info = self.authenticator.authorize()
667 except Exception:
668 token_info = None
669 if kwargs:
670 indata.update(kwargs)
671 # This is needed to log the user when authentication fails
672 cherrypy.request.login = "{}".format(indata.get("username", "-"))
673 outdata = token_info = self.authenticator.new_token(token_info, indata, cherrypy.request.remote)
674 cherrypy.session['Authorization'] = outdata["_id"]
675 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
676 # for logging
677 self._format_login(token_info)
678
679 # cherrypy.response.cookie["Authorization"] = outdata["id"]
680 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
681 elif method == "DELETE":
682 if not token_id and "id" in kwargs:
683 token_id = kwargs["id"]
684 elif not token_id:
685 token_info = self.authenticator.authorize()
686 # for logging
687 self._format_login(token_info)
688 token_id = token_info["_id"]
689 outdata = self.authenticator.del_token(token_id)
690 token_info = None
691 cherrypy.session['Authorization'] = "logout"
692 # cherrypy.response.cookie["Authorization"] = token_id
693 # cherrypy.response.cookie["Authorization"]['expires'] = 0
694 else:
695 raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
696 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100697
698 @cherrypy.expose
699 def test(self, *args, **kwargs):
700 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +0100701 if args and args[0] == "help":
tiernof717cbe2018-12-03 16:35:42 +0000702 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
tiernoe1281182018-05-22 12:24:36 +0200703 "sleep/<time>\nmessage/topic\n</pre></html>"
tiernof27c79b2018-03-12 17:08:42 +0100704
705 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +0100706 try:
707 # self.engine.load_dbase(cherrypy.request.app.config)
708 self.engine.create_admin()
709 return "Done. User 'admin', password 'admin' created"
710 except Exception:
711 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
712 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +0100713 elif args and args[0] == "file":
714 return cherrypy.lib.static.serve_file(cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1],
715 "text/plain", "attachment")
716 elif args and args[0] == "file2":
717 f_path = cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1]
718 f = open(f_path, "r")
719 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100720 return f
tierno55945e72018-04-06 16:40:27 +0200721
tiernof27c79b2018-03-12 17:08:42 +0100722 elif len(args) == 2 and args[0] == "db-clear":
tiernof717cbe2018-12-03 16:35:42 +0000723 deleted_info = self.engine.db.del_list(args[1], kwargs)
724 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
725 elif len(args) and args[0] == "fs-clear":
726 if len(args) >= 2:
727 folders = (args[1],)
728 else:
729 folders = self.engine.fs.dir_ls(".")
730 for folder in folders:
731 self.engine.fs.file_delete(folder)
732 return ",".join(folders) + " folders deleted\n"
tiernoc94c3df2018-02-09 15:38:54 +0100733 elif args and args[0] == "login":
734 if not cherrypy.request.headers.get("Authorization"):
735 cherrypy.response.headers["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
736 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
737 elif args and args[0] == "login2":
738 if not cherrypy.request.headers.get("Authorization"):
739 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
740 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
741 elif args and args[0] == "sleep":
742 sleep_time = 5
743 try:
744 sleep_time = int(args[1])
745 except Exception:
746 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
747 return self._format_out("Database already initialized")
748 thread_info = cherrypy.thread_data
749 print(thread_info)
750 time.sleep(sleep_time)
751 # thread_info
752 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +0200753 main_topic = args[1]
754 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +0100755 try:
tierno55945e72018-04-06 16:40:27 +0200756 if cherrypy.request.method == 'POST':
757 to_send = yaml.load(cherrypy.request.body)
758 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +0200759 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +0200760 return_text += " {}: {}\n".format(k, v)
761 elif cherrypy.request.method == 'GET':
762 for k, v in kwargs.items():
tiernob24258a2018-10-04 18:39:49 +0200763 self.engine.msg.write(main_topic, k, yaml.load(v))
tierno55945e72018-04-06 16:40:27 +0200764 return_text += " {}: {}\n".format(k, yaml.load(v))
tiernoc94c3df2018-02-09 15:38:54 +0100765 except Exception as e:
tierno55945e72018-04-06 16:40:27 +0200766 return_text += "Error: " + str(e)
767 return_text += "</pre></html>\n"
768 return return_text
tiernoc94c3df2018-02-09 15:38:54 +0100769
770 return_text = (
771 "<html><pre>\nheaders:\n args: {}\n".format(args) +
772 " kwargs: {}\n".format(kwargs) +
773 " headers: {}\n".format(cherrypy.request.headers) +
774 " path_info: {}\n".format(cherrypy.request.path_info) +
775 " query_string: {}\n".format(cherrypy.request.query_string) +
776 " session: {}\n".format(cherrypy.session) +
777 " cookie: {}\n".format(cherrypy.request.cookie) +
778 " method: {}\n".format(cherrypy.request.method) +
tiernof717cbe2018-12-03 16:35:42 +0000779 " session: {}\n".format(cherrypy.session.get('fieldname')) +
tiernoc94c3df2018-02-09 15:38:54 +0100780 " body:\n")
781 return_text += " length: {}\n".format(cherrypy.request.body.length)
782 if cherrypy.request.body.length:
783 return_text += " content: {}\n".format(
784 str(cherrypy.request.body.read(int(cherrypy.request.headers.get('Content-Length', 0)))))
785 if thread_info:
786 return_text += "thread: {}\n".format(thread_info)
787 return_text += "</pre></html>"
788 return return_text
789
tierno701018c2019-06-25 11:13:14 +0000790 @staticmethod
791 def _check_valid_url_method(method, *args):
tiernof27c79b2018-03-12 17:08:42 +0100792 if len(args) < 3:
tiernob24258a2018-10-04 18:39:49 +0200793 raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus.METHOD_NOT_ALLOWED)
tiernof27c79b2018-03-12 17:08:42 +0100794
tierno701018c2019-06-25 11:13:14 +0000795 reference = valid_url_methods
tiernof27c79b2018-03-12 17:08:42 +0100796 for arg in args:
797 if arg is None:
798 break
799 if not isinstance(reference, dict):
800 raise NbiException("URL contains unexpected extra items '{}'".format(arg),
801 HTTPStatus.METHOD_NOT_ALLOWED)
802
803 if arg in reference:
804 reference = reference[arg]
805 elif "<ID>" in reference:
806 reference = reference["<ID>"]
807 elif "*" in reference:
808 reference = reference["*"]
809 break
810 else:
811 raise NbiException("Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED)
812 if "TODO" in reference and method in reference["TODO"]:
813 raise NbiException("Method {} not supported yet for this URL".format(method), HTTPStatus.NOT_IMPLEMENTED)
tierno2236d202018-05-16 19:05:16 +0200814 elif "METHODS" in reference and method not in reference["METHODS"]:
tiernof27c79b2018-03-12 17:08:42 +0100815 raise NbiException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tierno701018c2019-06-25 11:13:14 +0000816 return reference["ROLE_PERMISSION"] + method.lower()
tiernof27c79b2018-03-12 17:08:42 +0100817
818 @staticmethod
tiernob24258a2018-10-04 18:39:49 +0200819 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +0100820 """
821 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +0200822 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +0100823 :param version:
tiernob24258a2018-10-04 18:39:49 +0200824 :param topic:
tiernof27c79b2018-03-12 17:08:42 +0100825 :param id:
826 :return: None
827 """
828 # 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 +0200829 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(main_topic, version, topic, id)
tiernof27c79b2018-03-12 17:08:42 +0100830 return
831
tierno65ca36d2019-02-12 19:27:52 +0100832 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000833 def _extract_query_string_operations(kwargs, method):
834 """
835
836 :param kwargs:
837 :return:
838 """
839 query_string_operations = []
840 if kwargs:
841 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
842 if qs in kwargs and kwargs[qs].lower() != "false":
843 query_string_operations.append(qs.lower() + ":" + method.lower())
844 return query_string_operations
845
846 @staticmethod
847 def _manage_admin_query(token_info, kwargs, method, _id):
tierno65ca36d2019-02-12 19:27:52 +0100848 """
849 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
850 Check that users has rights to use them and returs the admin_query
tierno701018c2019-06-25 11:13:14 +0000851 :param token_info: token_info rights obtained by token
tierno65ca36d2019-02-12 19:27:52 +0100852 :param kwargs: query string input.
853 :param method: http method: GET, POSST, PUT, ...
854 :param _id:
855 :return: admin_query dictionary with keys:
856 public: True, False or None
857 force: True or False
858 project_id: tuple with projects used for accessing an element
859 set_project: tuple with projects that a created element will belong to
860 method: show, list, delete, write
861 """
tierno701018c2019-06-25 11:13:14 +0000862 admin_query = {"force": False, "project_id": (token_info["project_id"], ), "username": token_info["username"],
863 "admin": token_info["admin"], "public": None}
tierno65ca36d2019-02-12 19:27:52 +0100864 if kwargs:
865 # FORCE
866 if "FORCE" in kwargs:
867 if kwargs["FORCE"].lower() != "false": # if None or True set force to True
868 admin_query["force"] = True
869 del kwargs["FORCE"]
870 # PUBLIC
871 if "PUBLIC" in kwargs:
872 if kwargs["PUBLIC"].lower() != "false": # if None or True set public to True
873 admin_query["public"] = True
874 else:
875 admin_query["public"] = False
876 del kwargs["PUBLIC"]
877 # ADMIN
878 if "ADMIN" in kwargs:
879 behave_as = kwargs.pop("ADMIN")
880 if behave_as.lower() != "false":
tierno701018c2019-06-25 11:13:14 +0000881 if not token_info["admin"]:
tierno65ca36d2019-02-12 19:27:52 +0100882 raise NbiException("Only admin projects can use 'ADMIN' query string", HTTPStatus.UNAUTHORIZED)
883 if not behave_as or behave_as.lower() == "true": # convert True, None to empty list
884 admin_query["project_id"] = ()
885 elif isinstance(behave_as, (list, tuple)):
886 admin_query["project_id"] = behave_as
887 else: # isinstance(behave_as, str)
888 admin_query["project_id"] = (behave_as, )
889 if "SET_PROJECT" in kwargs:
890 set_project = kwargs.pop("SET_PROJECT")
891 if not set_project:
892 admin_query["set_project"] = list(admin_query["project_id"])
893 else:
894 if isinstance(set_project, str):
895 set_project = (set_project, )
896 if admin_query["project_id"]:
897 for p in set_project:
898 if p not in admin_query["project_id"]:
899 raise NbiException("Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
900 "'ADMIN='{p}'".format(p=p), HTTPStatus.UNAUTHORIZED)
901 admin_query["set_project"] = set_project
902
903 # PROJECT_READ
904 # if "PROJECT_READ" in kwargs:
905 # admin_query["project"] = kwargs.pop("project")
tierno701018c2019-06-25 11:13:14 +0000906 # if admin_query["project"] == token_info["project_id"]:
tierno65ca36d2019-02-12 19:27:52 +0100907 if method == "GET":
908 if _id:
909 admin_query["method"] = "show"
910 else:
911 admin_query["method"] = "list"
912 elif method == "DELETE":
913 admin_query["method"] = "delete"
914 else:
915 admin_query["method"] = "write"
916 return admin_query
917
tiernoc94c3df2018-02-09 15:38:54 +0100918 @cherrypy.expose
tiernob24258a2018-10-04 18:39:49 +0200919 def default(self, main_topic=None, version=None, topic=None, _id=None, item=None, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000920 token_info = None
tiernof27c79b2018-03-12 17:08:42 +0100921 outdata = None
922 _format = None
tierno0f98af52018-03-19 10:28:22 +0100923 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +0200924 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +0200925 rollback = []
tierno701018c2019-06-25 11:13:14 +0000926 engine_session = None
tiernoc94c3df2018-02-09 15:38:54 +0100927 try:
tiernob24258a2018-10-04 18:39:49 +0200928 if not main_topic or not version or not topic:
929 raise NbiException("URL must contain at least 'main_topic/version/topic'",
930 HTTPStatus.METHOD_NOT_ALLOWED)
vijay.r35ef2f72019-04-30 17:55:49 +0530931 if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm", "nspm"):
tiernob24258a2018-10-04 18:39:49 +0200932 raise NbiException("URL main_topic '{}' not supported".format(main_topic),
933 HTTPStatus.METHOD_NOT_ALLOWED)
tiernoc94c3df2018-02-09 15:38:54 +0100934 if version != 'v1':
935 raise NbiException("URL version '{}' not supported".format(version), HTTPStatus.METHOD_NOT_ALLOWED)
936
tiernof27c79b2018-03-12 17:08:42 +0100937 if kwargs and "METHOD" in kwargs and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
938 method = kwargs.pop("METHOD")
939 else:
940 method = cherrypy.request.method
tierno65ca36d2019-02-12 19:27:52 +0100941
tierno701018c2019-06-25 11:13:14 +0000942 role_permission = self._check_valid_url_method(method, main_topic, version, topic, _id, item, *args)
943 query_string_operations = self._extract_query_string_operations(kwargs, method)
tiernob24258a2018-10-04 18:39:49 +0200944 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +0100945 return self.token(method, _id, kwargs)
946
tierno701018c2019-06-25 11:13:14 +0000947 token_info = self.authenticator.authorize(role_permission, query_string_operations)
948 engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
tiernof27c79b2018-03-12 17:08:42 +0100949 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +0200950 engine_topic = topic
951 if topic == "subscriptions":
952 engine_topic = main_topic + "_" + topic
vijay.r35ef2f72019-04-30 17:55:49 +0530953 if item and topic != "pm_jobs":
tiernob24258a2018-10-04 18:39:49 +0200954 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +0100955
tiernob24258a2018-10-04 18:39:49 +0200956 if main_topic == "nsd":
957 engine_topic = "nsds"
958 elif main_topic == "vnfpkgm":
959 engine_topic = "vnfds"
960 elif main_topic == "nslcm":
961 engine_topic = "nsrs"
962 if topic == "ns_lcm_op_occs":
963 engine_topic = "nslcmops"
964 if topic == "vnfrs" or topic == "vnf_instances":
965 engine_topic = "vnfrs"
garciadeblas9750c5a2018-10-15 16:20:35 +0200966 elif main_topic == "nst":
967 engine_topic = "nsts"
968 elif main_topic == "nsilcm":
969 engine_topic = "nsis"
970 if topic == "nsi_lcm_op_occs":
delacruzramoc061f562019-04-05 11:00:02 +0200971 engine_topic = "nsilcmops"
tiernob24258a2018-10-04 18:39:49 +0200972 elif main_topic == "pdu":
973 engine_topic = "pdus"
tierno65ca36d2019-02-12 19:27:52 +0100974 if engine_topic == "vims": # TODO this is for backward compatibility, it will be removed in the future
tiernob24258a2018-10-04 18:39:49 +0200975 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +0100976
977 if method == "GET":
Felipe Vicens07f31722018-10-29 15:16:44 +0100978 if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
garciadeblas9750c5a2018-10-15 16:20:35 +0200979 if item in ("vnfd", "nsd", "nst"):
tiernof27c79b2018-03-12 17:08:42 +0100980 path = "$DESCRIPTOR"
981 elif args:
982 path = args
tiernob24258a2018-10-04 18:39:49 +0200983 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +0100984 path = ()
985 else:
986 path = None
tierno701018c2019-06-25 11:13:14 +0000987 file, _format = self.engine.get_file(engine_session, engine_topic, _id, path,
tierno2236d202018-05-16 19:05:16 +0200988 cherrypy.request.headers.get("Accept"))
tiernof27c79b2018-03-12 17:08:42 +0100989 outdata = file
990 elif not _id:
tierno701018c2019-06-25 11:13:14 +0000991 outdata = self.engine.get_item_list(engine_session, engine_topic, kwargs)
tiernoc94c3df2018-02-09 15:38:54 +0100992 else:
vijay.r35ef2f72019-04-30 17:55:49 +0530993 if item == "reports":
994 # TODO check that project_id (_id in this context) has permissions
995 _id = args[0]
tierno701018c2019-06-25 11:13:14 +0000996 outdata = self.engine.get_item(engine_session, engine_topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100997 elif method == "POST":
tiernobdebce92019-07-01 15:36:49 +0000998 cherrypy.response.status = HTTPStatus.CREATED.value
garciadeblas9750c5a2018-10-15 16:20:35 +0200999 if topic in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"):
tiernof27c79b2018-03-12 17:08:42 +01001000 _id = cherrypy.request.headers.get("Transaction-Id")
1001 if not _id:
tiernobdebce92019-07-01 15:36:49 +00001002 _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, {}, None,
1003 cherrypy.request.headers)
tierno701018c2019-06-25 11:13:14 +00001004 completed = self.engine.upload_content(engine_session, engine_topic, _id, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +01001005 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +01001006 if completed:
tiernob24258a2018-10-04 18:39:49 +02001007 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001008 else:
1009 cherrypy.response.headers["Transaction-Id"] = _id
1010 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +02001011 elif topic == "ns_instances_content":
1012 # creates NSR
tiernobdebce92019-07-01 15:36:49 +00001013 _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs)
tiernob24258a2018-10-04 18:39:49 +02001014 # creates nslcmop
1015 indata["lcmOperationType"] = "instantiate"
1016 indata["nsInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001017 nslcmop_id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", indata, None)
tiernob24258a2018-10-04 18:39:49 +02001018 self._set_location_header(main_topic, version, topic, _id)
kuuse078f55e2019-05-16 19:24:21 +02001019 outdata = {"id": _id, "nslcmop_id": nslcmop_id}
tiernob24258a2018-10-04 18:39:49 +02001020 elif topic == "ns_instances" and item:
1021 indata["lcmOperationType"] = item
1022 indata["nsInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001023 _id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", indata, kwargs)
tiernob24258a2018-10-04 18:39:49 +02001024 self._set_location_header(main_topic, version, "ns_lcm_op_occs", _id)
tierno65acb4d2018-04-06 16:42:40 +02001025 outdata = {"id": _id}
1026 cherrypy.response.status = HTTPStatus.ACCEPTED.value
garciadeblas9750c5a2018-10-15 16:20:35 +02001027 elif topic == "netslice_instances_content":
Felipe Vicens07f31722018-10-29 15:16:44 +01001028 # creates NetSlice_Instance_record (NSIR)
tiernobdebce92019-07-01 15:36:49 +00001029 _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs)
Felipe Vicens07f31722018-10-29 15:16:44 +01001030 self._set_location_header(main_topic, version, topic, _id)
garciadeblas9750c5a2018-10-15 16:20:35 +02001031 indata["lcmOperationType"] = "instantiate"
Felipe Vicens126af572019-06-05 19:13:04 +02001032 indata["netsliceInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001033 nsilcmop_id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", indata, kwargs)
kuuse078f55e2019-05-16 19:24:21 +02001034 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
delacruzramoc061f562019-04-05 11:00:02 +02001035
garciadeblas9750c5a2018-10-15 16:20:35 +02001036 elif topic == "netslice_instances" and item:
1037 indata["lcmOperationType"] = item
Felipe Vicens126af572019-06-05 19:13:04 +02001038 indata["netsliceInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001039 _id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", indata, kwargs)
garciadeblas9750c5a2018-10-15 16:20:35 +02001040 self._set_location_header(main_topic, version, "nsi_lcm_op_occs", _id)
1041 outdata = {"id": _id}
1042 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +01001043 else:
tiernobdebce92019-07-01 15:36:49 +00001044 _id, op_id = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs,
1045 cherrypy.request.headers)
tiernob24258a2018-10-04 18:39:49 +02001046 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001047 outdata = {"id": _id}
tiernobdebce92019-07-01 15:36:49 +00001048 if op_id:
1049 outdata["op_id"] = op_id
1050 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernob24258a2018-10-04 18:39:49 +02001051 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tierno09c073e2018-04-26 13:36:48 +02001052
tiernoc94c3df2018-02-09 15:38:54 +01001053 elif method == "DELETE":
1054 if not _id:
tierno701018c2019-06-25 11:13:14 +00001055 outdata = self.engine.del_item_list(engine_session, engine_topic, kwargs)
tierno09c073e2018-04-26 13:36:48 +02001056 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +01001057 else: # len(args) > 1
tiernoe8631782018-12-21 13:31:52 +00001058 delete_in_process = False
tierno701018c2019-06-25 11:13:14 +00001059 if topic == "ns_instances_content" and not engine_session["force"]:
tiernob24258a2018-10-04 18:39:49 +02001060 nslcmop_desc = {
1061 "lcmOperationType": "terminate",
1062 "nsInstanceId": _id,
1063 "autoremove": True
1064 }
tiernobdebce92019-07-01 15:36:49 +00001065 opp_id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", nslcmop_desc, None)
tiernoe8631782018-12-21 13:31:52 +00001066 if opp_id:
1067 delete_in_process = True
1068 outdata = {"_id": opp_id}
1069 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tierno701018c2019-06-25 11:13:14 +00001070 elif topic == "netslice_instances_content" and not engine_session["force"]:
garciadeblas9750c5a2018-10-15 16:20:35 +02001071 nsilcmop_desc = {
1072 "lcmOperationType": "terminate",
Felipe Vicens126af572019-06-05 19:13:04 +02001073 "netsliceInstanceId": _id,
garciadeblas9750c5a2018-10-15 16:20:35 +02001074 "autoremove": True
1075 }
tiernobdebce92019-07-01 15:36:49 +00001076 opp_id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", nsilcmop_desc, None)
tiernoe8631782018-12-21 13:31:52 +00001077 if opp_id:
1078 delete_in_process = True
1079 outdata = {"_id": opp_id}
1080 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1081 if not delete_in_process:
tierno701018c2019-06-25 11:13:14 +00001082 self.engine.del_item(engine_session, engine_topic, _id)
tierno09c073e2018-04-26 13:36:48 +02001083 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tierno55ba2e62018-12-11 17:22:22 +00001084 if engine_topic in ("vim_accounts", "wim_accounts", "sdns"):
tierno09c073e2018-04-26 13:36:48 +02001085 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1086
tierno7ae10112018-05-18 14:36:02 +02001087 elif method in ("PUT", "PATCH"):
tiernobdebce92019-07-01 15:36:49 +00001088 op_id = None
tierno701018c2019-06-25 11:13:14 +00001089 if not indata and not kwargs and not engine_session.get("set_project"):
tiernoc94c3df2018-02-09 15:38:54 +01001090 raise NbiException("Nothing to update. Provide payload and/or query string",
1091 HTTPStatus.BAD_REQUEST)
garciadeblas9750c5a2018-10-15 16:20:35 +02001092 if item in ("nsd_content", "package_content", "nst_content") and method == "PUT":
tierno701018c2019-06-25 11:13:14 +00001093 completed = self.engine.upload_content(engine_session, engine_topic, _id, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +01001094 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +01001095 if not completed:
1096 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +01001097 else:
tiernobdebce92019-07-01 15:36:49 +00001098 op_id = self.engine.edit_item(engine_session, engine_topic, _id, indata, kwargs)
1099
1100 if op_id:
1101 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1102 outdata = {"op_id": op_id}
1103 else:
1104 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
1105 outdata = None
tiernoc94c3df2018-02-09 15:38:54 +01001106 else:
1107 raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tiernoa6bb45d2019-06-14 09:45:39 +00001108
1109 # if Role information changes, it is needed to reload the information of roles
1110 if topic == "roles" and method != "GET":
1111 self.authenticator.load_operation_to_allowed_roles()
tierno701018c2019-06-25 11:13:14 +00001112 return self._format_out(outdata, token_info, _format)
tiernob24258a2018-10-04 18:39:49 +02001113 except Exception as e:
tierno36ec8602018-11-02 17:27:11 +01001114 if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException,
delacruzramo01b15d32019-07-02 14:37:47 +02001115 ValidationError, AuthconnException)):
tiernob24258a2018-10-04 18:39:49 +02001116 http_code_value = cherrypy.response.status = e.http_code.value
1117 http_code_name = e.http_code.name
1118 cherrypy.log("Exception {}".format(e))
1119 else:
1120 http_code_value = cherrypy.response.status = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Felipe Vicense36ab852018-11-23 14:12:09 +01001121 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
tiernob24258a2018-10-04 18:39:49 +02001122 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +02001123 if hasattr(outdata, "close"): # is an open file
1124 outdata.close()
tiernob24258a2018-10-04 18:39:49 +02001125 error_text = str(e)
1126 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +02001127 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +02001128 try:
tiernocc103432018-10-19 14:10:35 +02001129 if rollback_item.get("operation") == "set":
1130 self.engine.db.set_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
1131 rollback_item["content"], fail_on_empty=False)
1132 else:
tiernoe8631782018-12-21 13:31:52 +00001133 self.engine.db.del_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
1134 fail_on_empty=False)
tierno3ace63c2018-05-03 17:51:43 +02001135 except Exception as e2:
tiernob24258a2018-10-04 18:39:49 +02001136 rollback_error_text = "Rollback Exception {}: {}".format(rollback_item, e2)
1137 cherrypy.log(rollback_error_text)
1138 error_text += ". " + rollback_error_text
1139 # if isinstance(e, MsgException):
1140 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1141 # engine_topic[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +01001142 problem_details = {
tiernob24258a2018-10-04 18:39:49 +02001143 "code": http_code_name,
1144 "status": http_code_value,
1145 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +01001146 }
tierno701018c2019-06-25 11:13:14 +00001147 return self._format_out(problem_details, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001148 # raise cherrypy.HTTPError(e.http_code.value, str(e))
tiernoa5035702019-07-29 08:54:42 +00001149 finally:
1150 if token_info:
1151 self._format_login(token_info)
1152 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
1153 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1154 if outdata.get(logging_id):
1155 cherrypy.request.login += ";{}={}".format(logging_id, outdata[logging_id][:36])
tiernoc94c3df2018-02-09 15:38:54 +01001156
1157
tiernoc94c3df2018-02-09 15:38:54 +01001158def _start_service():
1159 """
1160 Callback function called when cherrypy.engine starts
1161 Override configuration with env variables
1162 Set database, storage, message configuration
1163 Init database with admin/admin user password
1164 """
tierno932499c2019-01-28 17:28:10 +00001165 global nbi_server
1166 global subscription_thread
tiernoc94c3df2018-02-09 15:38:54 +01001167 cherrypy.log.error("Starting osm_nbi")
1168 # update general cherrypy configuration
1169 update_dict = {}
1170
1171 engine_config = cherrypy.tree.apps['/osm'].config
1172 for k, v in environ.items():
1173 if not k.startswith("OSMNBI_"):
1174 continue
tiernoe1281182018-05-22 12:24:36 +02001175 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +01001176 if not k2:
1177 continue
1178 try:
1179 # update static configuration
1180 if k == 'OSMNBI_STATIC_DIR':
1181 engine_config["/static"]['tools.staticdir.dir'] = v
1182 engine_config["/static"]['tools.staticdir.on'] = True
1183 elif k == 'OSMNBI_SOCKET_PORT' or k == 'OSMNBI_SERVER_PORT':
1184 update_dict['server.socket_port'] = int(v)
1185 elif k == 'OSMNBI_SOCKET_HOST' or k == 'OSMNBI_SERVER_HOST':
1186 update_dict['server.socket_host'] = v
tiernof5298be2018-05-16 14:43:57 +02001187 elif k1 in ("server", "test", "auth", "log"):
1188 update_dict[k1 + '.' + k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001189 elif k1 in ("message", "database", "storage", "authentication"):
tiernof5298be2018-05-16 14:43:57 +02001190 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001191 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +01001192 engine_config[k1][k2] = int(v)
1193 else:
1194 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001195
tiernoc94c3df2018-02-09 15:38:54 +01001196 except ValueError as e:
1197 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1198 except Exception as e:
1199 cherrypy.log.warn("skipping environ '{}' on exception '{}'".format(k, e))
1200
1201 if update_dict:
1202 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +02001203 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001204
1205 # logging cherrypy
1206 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1207 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
1208 logger_server = logging.getLogger("cherrypy.error")
1209 logger_access = logging.getLogger("cherrypy.access")
1210 logger_cherry = logging.getLogger("cherrypy")
1211 logger_nbi = logging.getLogger("nbi")
1212
tiernof5298be2018-05-16 14:43:57 +02001213 if "log.file" in engine_config["global"]:
1214 file_handler = logging.handlers.RotatingFileHandler(engine_config["global"]["log.file"],
tiernoc94c3df2018-02-09 15:38:54 +01001215 maxBytes=100e6, backupCount=9, delay=0)
1216 file_handler.setFormatter(log_formatter_simple)
1217 logger_cherry.addHandler(file_handler)
1218 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +02001219 # log always to standard output
1220 for format_, logger in {"nbi.server %(filename)s:%(lineno)s": logger_server,
1221 "nbi.access %(filename)s:%(lineno)s": logger_access,
1222 "%(name)s %(filename)s:%(lineno)s": logger_nbi
1223 }.items():
1224 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
1225 log_formatter_cherry = logging.Formatter(log_format_cherry, datefmt='%Y-%m-%dT%H:%M:%S')
1226 str_handler = logging.StreamHandler()
1227 str_handler.setFormatter(log_formatter_cherry)
1228 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +01001229
tiernof5298be2018-05-16 14:43:57 +02001230 if engine_config["global"].get("log.level"):
1231 logger_cherry.setLevel(engine_config["global"]["log.level"])
1232 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +01001233
1234 # logging other modules
1235 for k1, logname in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
1236 engine_config[k1]["logger_name"] = logname
1237 logger_module = logging.getLogger(logname)
1238 if "logfile" in engine_config[k1]:
1239 file_handler = logging.handlers.RotatingFileHandler(engine_config[k1]["logfile"],
tierno2236d202018-05-16 19:05:16 +02001240 maxBytes=100e6, backupCount=9, delay=0)
tiernoc94c3df2018-02-09 15:38:54 +01001241 file_handler.setFormatter(log_formatter_simple)
1242 logger_module.addHandler(file_handler)
1243 if "loglevel" in engine_config[k1]:
1244 logger_module.setLevel(engine_config[k1]["loglevel"])
1245 # TODO add more entries, e.g.: storage
1246 cherrypy.tree.apps['/osm'].root.engine.start(engine_config)
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001247 cherrypy.tree.apps['/osm'].root.authenticator.start(engine_config)
tiernod985a8d2018-10-19 14:12:28 +02001248 cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version)
1249 cherrypy.tree.apps['/osm'].root.authenticator.init_db(target_version=auth_database_version)
tiernobee508e2019-01-21 11:21:49 +00001250
tierno932499c2019-01-28 17:28:10 +00001251 # start subscriptions thread:
1252 subscription_thread = SubscriptionThread(config=engine_config, engine=nbi_server.engine)
1253 subscription_thread.start()
1254 # Do not capture except SubscriptionException
1255
tiernobee508e2019-01-21 11:21:49 +00001256 # load and print version. Ignore possible errors, e.g. file not found
1257 try:
tierno9c630112019-08-29 14:21:41 +00001258 backend = engine_config["authentication"]["backend"]
1259 nbi_version
1260 cherrypy.log.error("Starting OSM NBI Version '{}' with '{}' authentication backend"
1261 .format(nbi_version + " " + nbi_version_date, backend))
tiernobee508e2019-01-21 11:21:49 +00001262 except Exception:
1263 pass
tiernoc94c3df2018-02-09 15:38:54 +01001264
1265
1266def _stop_service():
1267 """
1268 Callback function called when cherrypy.engine stops
1269 TODO: Ending database connections.
1270 """
tierno932499c2019-01-28 17:28:10 +00001271 global subscription_thread
tierno65ca36d2019-02-12 19:27:52 +01001272 if subscription_thread:
1273 subscription_thread.terminate()
tierno932499c2019-01-28 17:28:10 +00001274 subscription_thread = None
tiernoc94c3df2018-02-09 15:38:54 +01001275 cherrypy.tree.apps['/osm'].root.engine.stop()
1276 cherrypy.log.error("Stopping osm_nbi")
1277
tierno2236d202018-05-16 19:05:16 +02001278
tiernof5298be2018-05-16 14:43:57 +02001279def nbi(config_file):
tierno932499c2019-01-28 17:28:10 +00001280 global nbi_server
tiernoc94c3df2018-02-09 15:38:54 +01001281 # conf = {
1282 # '/': {
1283 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1284 # 'tools.sessions.on': True,
1285 # 'tools.response_headers.on': True,
1286 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1287 # }
1288 # }
1289 # cherrypy.Server.ssl_module = 'builtin'
1290 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1291 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1292 # cherrypy.Server.thread_pool = 10
1293 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1294
1295 # cherrypy.config.update({'tools.auth_basic.on': True,
1296 # 'tools.auth_basic.realm': 'localhost',
1297 # 'tools.auth_basic.checkpassword': validate_password})
tierno932499c2019-01-28 17:28:10 +00001298 nbi_server = Server()
tiernoc94c3df2018-02-09 15:38:54 +01001299 cherrypy.engine.subscribe('start', _start_service)
1300 cherrypy.engine.subscribe('stop', _stop_service)
tierno932499c2019-01-28 17:28:10 +00001301 cherrypy.quickstart(nbi_server, '/osm', config_file)
tiernof5298be2018-05-16 14:43:57 +02001302
1303
1304def usage():
1305 print("""Usage: {} [options]
1306 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1307 -h|--help: shows this help
1308 """.format(sys.argv[0]))
tierno2236d202018-05-16 19:05:16 +02001309 # --log-socket-host HOST: send logs to this host")
1310 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +01001311
1312
1313if __name__ == '__main__':
tiernof5298be2018-05-16 14:43:57 +02001314 try:
1315 # load parameters and configuration
1316 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
1317 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1318 config_file = None
1319 for o, a in opts:
1320 if o in ("-h", "--help"):
1321 usage()
1322 sys.exit()
1323 elif o in ("-c", "--config"):
1324 config_file = a
1325 # elif o == "--log-socket-port":
1326 # log_socket_port = a
1327 # elif o == "--log-socket-host":
1328 # log_socket_host = a
1329 # elif o == "--log-file":
1330 # log_file = a
1331 else:
1332 assert False, "Unhandled option"
1333 if config_file:
1334 if not path.isfile(config_file):
1335 print("configuration file '{}' that not exist".format(config_file), file=sys.stderr)
1336 exit(1)
1337 else:
1338 for config_file in (__file__[:__file__.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
1339 if path.isfile(config_file):
1340 break
1341 else:
1342 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys.stderr)
1343 exit(1)
1344 nbi(config_file)
1345 except getopt.GetoptError as e:
1346 print(str(e), file=sys.stderr)
1347 # usage()
1348 exit(1)