blob: 33147b73e836875f5f1449cc69c2418d8b82dc51 [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
21import html_out as html
22import logging
tiernof5298be2018-05-16 14:43:57 +020023import logging.handlers
24import getopt
25import sys
Eduardo Sousa2f988212018-07-26 01:04:11 +010026
delacruzramo01b15d32019-07-02 14:37:47 +020027from authconn import AuthException, AuthconnException
Eduardo Sousa2f988212018-07-26 01:04:11 +010028from auth import Authenticator
tiernoc94c3df2018-02-09 15:38:54 +010029from engine import Engine, EngineException
tierno932499c2019-01-28 17:28:10 +000030from subscriptions import SubscriptionThread
tierno36ec8602018-11-02 17:27:11 +010031from 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
tiernoc94c3df2018-02-09 15:38:54 +010038
39__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
tiernodfe09572018-04-24 10:41:10 +020040
tiernodfe09572018-04-24 10:41:10 +020041__version__ = "0.1.3"
tiernobee508e2019-01-21 11:21:49 +000042version_date = "Jan 2019"
tierno1f029d82019-06-13 22:37:04 +000043database_version = '1.2'
Eduardo Sousa819d34c2018-07-31 01:20:02 +010044auth_database_version = '1.0'
tierno932499c2019-01-28 17:28:10 +000045nbi_server = None # instance of Server class
46subscription_thread = None # instance of SubscriptionThread class
47
tiernoc94c3df2018-02-09 15:38:54 +010048
49"""
tiernof27c79b2018-03-12 17:08:42 +010050North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
tiernoc94c3df2018-02-09 15:38:54 +010051URL: /osm GET POST PUT DELETE PATCH
garciadeblas9750c5a2018-10-15 16:20:35 +020052 /nsd/v1
tierno2236d202018-05-16 19:05:16 +020053 /ns_descriptors_content O O
54 /<nsdInfoId> O O O O
tiernoc94c3df2018-02-09 15:38:54 +010055 /ns_descriptors O5 O5
56 /<nsdInfoId> O5 O5 5
57 /nsd_content O5 O5
tiernof27c79b2018-03-12 17:08:42 +010058 /nsd O
59 /artifacts[/<artifactPath>] O
tiernoc94c3df2018-02-09 15:38:54 +010060 /pnf_descriptors 5 5
61 /<pnfdInfoId> 5 5 5
62 /pnfd_content 5 5
tiernof27c79b2018-03-12 17:08:42 +010063 /subscriptions 5 5
64 /<subscriptionId> 5 X
tiernoc94c3df2018-02-09 15:38:54 +010065
66 /vnfpkgm/v1
tierno55945e72018-04-06 16:40:27 +020067 /vnf_packages_content O O
tierno2236d202018-05-16 19:05:16 +020068 /<vnfPkgId> O O
tiernoc94c3df2018-02-09 15:38:54 +010069 /vnf_packages O5 O5
70 /<vnfPkgId> O5 O5 5
tiernoc94c3df2018-02-09 15:38:54 +010071 /package_content O5 O5
72 /upload_from_uri X
tiernof27c79b2018-03-12 17:08:42 +010073 /vnfd O5
74 /artifacts[/<artifactPath>] O5
75 /subscriptions X X
76 /<subscriptionId> X X
tiernoc94c3df2018-02-09 15:38:54 +010077
78 /nslcm/v1
tiernof27c79b2018-03-12 17:08:42 +010079 /ns_instances_content O O
tierno2236d202018-05-16 19:05:16 +020080 /<nsInstanceId> O O
tiernof27c79b2018-03-12 17:08:42 +010081 /ns_instances 5 5
tierno95692442018-05-24 18:05:28 +020082 /<nsInstanceId> O5 O5
tierno65acb4d2018-04-06 16:42:40 +020083 instantiate O5
84 terminate O5
85 action O
86 scale O5
87 heal 5
tiernoc94c3df2018-02-09 15:38:54 +010088 /ns_lcm_op_occs 5 5
89 /<nsLcmOpOccId> 5 5 5
90 TO BE COMPLETED 5 5
tiernof759d822018-06-11 18:54:54 +020091 /vnf_instances (also vnfrs for compatibility) O
92 /<vnfInstanceId> O
tiernof27c79b2018-03-12 17:08:42 +010093 /subscriptions 5 5
94 /<subscriptionId> 5 X
garciadeblas9750c5a2018-10-15 16:20:35 +020095
tiernocb83c942018-09-24 17:28:13 +020096 /pdu/v1
tierno032916c2019-03-22 13:27:12 +000097 /pdu_descriptors O O
tiernocb83c942018-09-24 17:28:13 +020098 /<id> O O O O
garciadeblas9750c5a2018-10-15 16:20:35 +020099
tiernof27c79b2018-03-12 17:08:42 +0100100 /admin/v1
101 /tokens O O
tierno2236d202018-05-16 19:05:16 +0200102 /<id> O O
tiernof27c79b2018-03-12 17:08:42 +0100103 /users O O
tiernocd54a4a2018-09-12 16:40:35 +0200104 /<id> O O O O
tiernof27c79b2018-03-12 17:08:42 +0100105 /projects O O
tierno2236d202018-05-16 19:05:16 +0200106 /<id> O O
tierno55ba2e62018-12-11 17:22:22 +0000107 /vim_accounts (also vims for compatibility) O O
108 /<id> O O O
109 /wim_accounts O O
tierno2236d202018-05-16 19:05:16 +0200110 /<id> O O O
tierno0f98af52018-03-19 10:28:22 +0100111 /sdns O O
tierno2236d202018-05-16 19:05:16 +0200112 /<id> O O O
tiernoc94c3df2018-02-09 15:38:54 +0100113
garciadeblas9750c5a2018-10-15 16:20:35 +0200114 /nst/v1 O O
115 /netslice_templates_content O O
116 /<nstInfoId> O O O O
117 /netslice_templates O O
118 /<nstInfoId> O O O
119 /nst_content O O
120 /nst O
121 /artifacts[/<artifactPath>] O
122 /subscriptions X X
123 /<subscriptionId> X X
124
125 /nsilcm/v1
126 /netslice_instances_content O O
127 /<SliceInstanceId> O O
128 /netslice_instances O O
129 /<SliceInstanceId> O O
130 instantiate O
131 terminate O
132 action O
133 /nsi_lcm_op_occs O O
134 /<nsiLcmOpOccId> O O O
135 /subscriptions X X
136 /<subscriptionId> X X
137
tierno2236d202018-05-16 19:05:16 +0200138query string:
139 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
tierno36ec8602018-11-02 17:27:11 +0100140 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
141 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
142 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
143 attrName := string
tierno2236d202018-05-16 19:05:16 +0200144 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
145 item of the array, that is, pass if any item of the array pass the filter.
146 It allows both ne and neq for not equal
147 TODO: 4.3.3 Attribute selectors
148 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
tiernoc94c3df2018-02-09 15:38:54 +0100149 (none) … same as “exclude_default”
150 all_fields … all attributes.
tierno2236d202018-05-16 19:05:16 +0200151 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
152 conditionally mandatory, and that are not provided in <list>.
153 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
154 are not conditionally mandatory, and that are provided in <list>.
155 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
156 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
157 the particular resource
158 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
159 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
160 present specification for the particular resource, but that are not part of <list>
tierno65ca36d2019-02-12 19:27:52 +0100161 Additionally it admits some administrator values:
162 FORCE: To force operations skipping dependency checkings
163 ADMIN: To act as an administrator or a different project
164 PUBLIC: To get public descriptors or set a descriptor as public
165 SET_PROJECT: To make a descriptor available for other project
166
tiernoc94c3df2018-02-09 15:38:54 +0100167Header field name Reference Example Descriptions
168 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
169 This header field shall be present if the response is expected to have a non-empty message body.
170 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
171 This header field shall be present if the request has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200172 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
173 Details are specified in clause 4.5.3.
tiernoc94c3df2018-02-09 15:38:54 +0100174 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
175Header field name Reference Example Descriptions
176 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
177 This header field shall be present if the response has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200178 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
179 new resource has been created.
tiernoc94c3df2018-02-09 15:38:54 +0100180 This header field shall be present if the response status code is 201 or 3xx.
tierno2236d202018-05-16 19:05:16 +0200181 In the present document this header field is also used if the response status code is 202 and a new resource was
182 created.
183 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
184 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
185 token.
186 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
187 certain resources.
188 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
189 response, and the total length of the file.
tiernoc94c3df2018-02-09 15:38:54 +0100190 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
tiernoc94c3df2018-02-09 15:38:54 +0100191"""
192
tierno701018c2019-06-25 11:13:14 +0000193valid_query_string = ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
194# ^ Contains possible administrative query string words:
195# ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
196# (not owned by my session project).
197# PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
198# FORCE=True(by default)|False: Force edition/deletion operations
199# SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
200
201valid_url_methods = {
202 # contains allowed URL and methods, and the role_permission name
203 "admin": {
204 "v1": {
205 "tokens": {"METHODS": ("GET", "POST", "DELETE"),
206 "ROLE_PERMISSION": "tokens:",
207 "<ID>": {"METHODS": ("GET", "DELETE"),
208 "ROLE_PERMISSION": "tokens:id:"
209 }
210 },
211 "users": {"METHODS": ("GET", "POST"),
212 "ROLE_PERMISSION": "users:",
213 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
214 "ROLE_PERMISSION": "users:id:"
215 }
216 },
217 "projects": {"METHODS": ("GET", "POST"),
218 "ROLE_PERMISSION": "projects:",
219 "<ID>": {"METHODS": ("GET", "DELETE", "PUT"),
220 "ROLE_PERMISSION": "projects:id:"}
221 },
222 "roles": {"METHODS": ("GET", "POST"),
223 "ROLE_PERMISSION": "roles:",
224 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PUT"),
225 "ROLE_PERMISSION": "roles:id:"
226 }
227 },
228 "vims": {"METHODS": ("GET", "POST"),
229 "ROLE_PERMISSION": "vims:",
230 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
231 "ROLE_PERMISSION": "vims:id:"
232 }
233 },
234 "vim_accounts": {"METHODS": ("GET", "POST"),
235 "ROLE_PERMISSION": "vim_accounts:",
236 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
237 "ROLE_PERMISSION": "vim_accounts:id:"
238 }
239 },
240 "wim_accounts": {"METHODS": ("GET", "POST"),
241 "ROLE_PERMISSION": "wim_accounts:",
242 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
243 "ROLE_PERMISSION": "wim_accounts:id:"
244 }
245 },
246 "sdns": {"METHODS": ("GET", "POST"),
247 "ROLE_PERMISSION": "sdn_controllers:",
248 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
249 "ROLE_PERMISSION": "sdn_controllers:id:"
250 }
251 },
252 }
253 },
254 "pdu": {
255 "v1": {
256 "pdu_descriptors": {"METHODS": ("GET", "POST"),
257 "ROLE_PERMISSION": "pduds:",
258 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
259 "ROLE_PERMISSION": "pduds:id:"
260 }
261 },
262 }
263 },
264 "nsd": {
265 "v1": {
266 "ns_descriptors_content": {"METHODS": ("GET", "POST"),
267 "ROLE_PERMISSION": "nsds:",
268 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
269 "ROLE_PERMISSION": "nsds:id:"
270 }
271 },
272 "ns_descriptors": {"METHODS": ("GET", "POST"),
273 "ROLE_PERMISSION": "nsds:",
274 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
275 "ROLE_PERMISSION": "nsds:id:",
276 "nsd_content": {"METHODS": ("GET", "PUT"),
277 "ROLE_PERMISSION": "nsds:id:content:",
278 },
279 "nsd": {"METHODS": ("GET",), # descriptor inside package
280 "ROLE_PERMISSION": "nsds:id:content:"
281 },
282 "artifacts": {"*": {"METHODS": ("GET",),
283 "ROLE_PERMISSION": "nsds:id:nsd_artifact:"
284 }
285 }
286 }
287 },
288 "pnf_descriptors": {"TODO": ("GET", "POST"),
289 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
290 "pnfd_content": {"TODO": ("GET", "PUT")}
291 }
292 },
293 "subscriptions": {"TODO": ("GET", "POST"),
294 "<ID>": {"TODO": ("GET", "DELETE")}
295 },
296 }
297 },
298 "vnfpkgm": {
299 "v1": {
300 "vnf_packages_content": {"METHODS": ("GET", "POST"),
301 "ROLE_PERMISSION": "vnfds:",
302 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
303 "ROLE_PERMISSION": "vnfds:id:"}
304 },
305 "vnf_packages": {"METHODS": ("GET", "POST"),
306 "ROLE_PERMISSION": "vnfds:",
307 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
308 "ROLE_PERMISSION": "vnfds:id:",
309 "package_content": {"METHODS": ("GET", "PUT"), # package
310 "ROLE_PERMISSION": "vnfds:id:",
311 "upload_from_uri": {"METHODS": (),
312 "TODO": ("POST", ),
313 "ROLE_PERMISSION": "vnfds:id:upload:"
314 }
315 },
316 "vnfd": {"METHODS": ("GET", ), # descriptor inside package
317 "ROLE_PERMISSION": "vnfds:id:content:"
318 },
319 "artifacts": {"*": {"METHODS": ("GET", ),
320 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:"
321 }
322 }
323 }
324 },
325 "subscriptions": {"TODO": ("GET", "POST"),
326 "<ID>": {"TODO": ("GET", "DELETE")}
327 },
328 }
329 },
330 "nslcm": {
331 "v1": {
332 "ns_instances_content": {"METHODS": ("GET", "POST"),
333 "ROLE_PERMISSION": "ns_instances:",
334 "<ID>": {"METHODS": ("GET", "DELETE"),
335 "ROLE_PERMISSION": "ns_instances:id:"
336 }
337 },
338 "ns_instances": {"METHODS": ("GET", "POST"),
339 "ROLE_PERMISSION": "ns_instances:",
340 "<ID>": {"METHODS": ("GET", "DELETE"),
341 "ROLE_PERMISSION": "ns_instances:id:",
342 "scale": {"METHODS": ("POST",),
343 "ROLE_PERMISSION": "ns_instances:id:scale:"
344 },
345 "terminate": {"METHODS": ("POST",),
346 "ROLE_PERMISSION": "ns_instances:id:terminate:"
347 },
348 "instantiate": {"METHODS": ("POST",),
349 "ROLE_PERMISSION": "ns_instances:id:instantiate:"
350 },
351 "action": {"METHODS": ("POST",),
352 "ROLE_PERMISSION": "ns_instances:id:action:"
353 },
354 }
355 },
356 "ns_lcm_op_occs": {"METHODS": ("GET",),
357 "ROLE_PERMISSION": "ns_instances:opps:",
358 "<ID>": {"METHODS": ("GET",),
359 "ROLE_PERMISSION": "ns_instances:opps:id:"
360 },
361 },
362 "vnfrs": {"METHODS": ("GET",),
363 "ROLE_PERMISSION": "vnf_instances:",
364 "<ID>": {"METHODS": ("GET",),
365 "ROLE_PERMISSION": "vnf_instances:id:"
366 }
367 },
368 "vnf_instances": {"METHODS": ("GET",),
369 "ROLE_PERMISSION": "vnf_instances:",
370 "<ID>": {"METHODS": ("GET",),
371 "ROLE_PERMISSION": "vnf_instances:id:"
372 }
373 },
374 }
375 },
376 "nst": {
377 "v1": {
378 "netslice_templates_content": {"METHODS": ("GET", "POST"),
379 "ROLE_PERMISSION": "slice_templates:",
380 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
381 "ROLE_PERMISSION": "slice_templates:id:", }
382 },
383 "netslice_templates": {"METHODS": ("GET", "POST"),
384 "ROLE_PERMISSION": "slice_templates:",
385 "<ID>": {"METHODS": ("GET", "DELETE"),
386 "TODO": ("PATCH",),
387 "ROLE_PERMISSION": "slice_templates:id:",
388 "nst_content": {"METHODS": ("GET", "PUT"),
389 "ROLE_PERMISSION": "slice_templates:id:content:"
390 },
391 "nst": {"METHODS": ("GET",), # descriptor inside package
392 "ROLE_PERMISSION": "slice_templates:id:content:"
393 },
394 "artifacts": {"*": {"METHODS": ("GET",),
395 "ROLE_PERMISSION": "slice_templates:id:content:"
396 }
397 }
398 }
399 },
400 "subscriptions": {"TODO": ("GET", "POST"),
401 "<ID>": {"TODO": ("GET", "DELETE")}
402 },
403 }
404 },
405 "nsilcm": {
406 "v1": {
407 "netslice_instances_content": {"METHODS": ("GET", "POST"),
408 "ROLE_PERMISSION": "slice_instances:",
409 "<ID>": {"METHODS": ("GET", "DELETE"),
410 "ROLE_PERMISSION": "slice_instances:id:"
411 }
412 },
413 "netslice_instances": {"METHODS": ("GET", "POST"),
414 "ROLE_PERMISSION": "slice_instances:",
415 "<ID>": {"METHODS": ("GET", "DELETE"),
416 "ROLE_PERMISSION": "slice_instances:id:",
417 "terminate": {"METHODS": ("POST",),
418 "ROLE_PERMISSION": "slice_instances:id:terminate:"
419 },
420 "instantiate": {"METHODS": ("POST",),
421 "ROLE_PERMISSION": "slice_instances:id:instantiate:"
422 },
423 "action": {"METHODS": ("POST",),
424 "ROLE_PERMISSION": "slice_instances:id:action:"
425 },
426 }
427 },
428 "nsi_lcm_op_occs": {"METHODS": ("GET",),
429 "ROLE_PERMISSION": "slice_instances:opps:",
430 "<ID>": {"METHODS": ("GET",),
431 "ROLE_PERMISSION": "slice_instances:opps:id:",
432 },
433 },
434 }
435 },
436 "nspm": {
437 "v1": {
438 "pm_jobs": {
439 "<ID>": {
440 "reports": {
441 "<ID>": {"METHODS": ("GET",),
442 "ROLE_PERMISSION": "reports:id:",
443 }
444 }
445 },
446 },
447 },
448 },
449}
450
tiernoc94c3df2018-02-09 15:38:54 +0100451
452class NbiException(Exception):
453
454 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
455 Exception.__init__(self, message)
456 self.http_code = http_code
457
458
459class Server(object):
460 instance = 0
461 # to decode bytes to str
462 reader = getreader("utf-8")
463
464 def __init__(self):
465 self.instance += 1
466 self.engine = Engine()
tierno701018c2019-06-25 11:13:14 +0000467 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
tiernoc94c3df2018-02-09 15:38:54 +0100468
tiernoc94c3df2018-02-09 15:38:54 +0100469 def _format_in(self, kwargs):
470 try:
471 indata = None
472 if cherrypy.request.body.length:
473 error_text = "Invalid input format "
474
475 if "Content-Type" in cherrypy.request.headers:
476 if "application/json" in cherrypy.request.headers["Content-Type"]:
477 error_text = "Invalid json format "
478 indata = json.load(self.reader(cherrypy.request.body))
gcalvinode4adfe2018-10-30 11:46:09 +0100479 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100480 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
481 error_text = "Invalid yaml format "
482 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100483 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100484 elif "application/binary" in cherrypy.request.headers["Content-Type"] or \
485 "application/gzip" in cherrypy.request.headers["Content-Type"] or \
tiernof27c79b2018-03-12 17:08:42 +0100486 "application/zip" in cherrypy.request.headers["Content-Type"] or \
487 "text/plain" in cherrypy.request.headers["Content-Type"]:
488 indata = cherrypy.request.body # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100489 elif "multipart/form-data" in cherrypy.request.headers["Content-Type"]:
490 if "descriptor_file" in kwargs:
491 filecontent = kwargs.pop("descriptor_file")
492 if not filecontent.file:
493 raise NbiException("empty file or content", HTTPStatus.BAD_REQUEST)
tiernof27c79b2018-03-12 17:08:42 +0100494 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100495 if filecontent.content_type.value:
496 cherrypy.request.headers["Content-Type"] = filecontent.content_type.value
497 else:
498 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
499 # "Only 'Content-Type' of type 'application/json' or
500 # 'application/yaml' for input format are available")
501 error_text = "Invalid yaml format "
502 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100503 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100504 else:
505 error_text = "Invalid yaml format "
506 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100507 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100508 if not indata:
509 indata = {}
510
tiernoc94c3df2018-02-09 15:38:54 +0100511 format_yaml = False
512 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
513 format_yaml = True
514
515 for k, v in kwargs.items():
516 if isinstance(v, str):
517 if v == "":
518 kwargs[k] = None
519 elif format_yaml:
520 try:
521 kwargs[k] = yaml.load(v)
tiernoe1281182018-05-22 12:24:36 +0200522 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100523 pass
524 elif k.endswith(".gt") or k.endswith(".lt") or k.endswith(".gte") or k.endswith(".lte"):
525 try:
526 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200527 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100528 try:
529 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200530 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100531 pass
532 elif v.find(",") > 0:
533 kwargs[k] = v.split(",")
534 elif isinstance(v, (list, tuple)):
535 for index in range(0, len(v)):
536 if v[index] == "":
537 v[index] = None
538 elif format_yaml:
539 try:
540 v[index] = yaml.load(v[index])
tiernoe1281182018-05-22 12:24:36 +0200541 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100542 pass
543
tiernof27c79b2018-03-12 17:08:42 +0100544 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100545 except (ValueError, yaml.YAMLError) as exc:
546 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
547 except KeyError as exc:
548 raise NbiException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
tiernob92094f2018-05-11 13:44:22 +0200549 except Exception as exc:
550 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100551
552 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000553 def _format_out(data, token_info=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100554 """
555 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100556 :param data: response to be sent. Can be a dict, text or file
tierno701018c2019-06-25 11:13:14 +0000557 :param token_info: Contains among other username and project
tiernof27c79b2018-03-12 17:08:42 +0100558 :param _format: The format to be set as Content-Type ir data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100559 :return: None
560 """
tierno0f98af52018-03-19 10:28:22 +0100561 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100562 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100563 if accept and "text/html" in accept:
tierno701018c2019-06-25 11:13:14 +0000564 return html.format(data, cherrypy.request, cherrypy.response, token_info)
tierno09c073e2018-04-26 13:36:48 +0200565 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100566 return
567 elif hasattr(data, "read"): # file object
568 if _format:
569 cherrypy.response.headers["Content-Type"] = _format
570 elif "b" in data.mode: # binariy asssumig zip
571 cherrypy.response.headers["Content-Type"] = 'application/zip'
572 else:
573 cherrypy.response.headers["Content-Type"] = 'text/plain'
574 # TODO check that cherrypy close file. If not implement pending things to close per thread next
575 return data
tierno0f98af52018-03-19 10:28:22 +0100576 if accept:
tiernoc94c3df2018-02-09 15:38:54 +0100577 if "application/json" in accept:
578 cherrypy.response.headers["Content-Type"] = 'application/json; charset=utf-8'
579 a = json.dumps(data, indent=4) + "\n"
580 return a.encode("utf8")
581 elif "text/html" in accept:
tierno701018c2019-06-25 11:13:14 +0000582 return html.format(data, cherrypy.request, cherrypy.response, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100583
tiernof27c79b2018-03-12 17:08:42 +0100584 elif "application/yaml" in accept or "*/*" in accept or "text/plain" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100585 pass
tiernof717cbe2018-12-03 16:35:42 +0000586 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
587 elif cherrypy.response.status >= 400:
tiernoc94c3df2018-02-09 15:38:54 +0100588 raise cherrypy.HTTPError(HTTPStatus.NOT_ACCEPTABLE.value,
589 "Only 'Accept' of type 'application/json' or 'application/yaml' "
590 "for output format are available")
591 cherrypy.response.headers["Content-Type"] = 'application/yaml'
592 return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False,
593 encoding='utf-8', allow_unicode=True) # , canonical=True, default_style='"'
594
595 @cherrypy.expose
596 def index(self, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000597 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100598 try:
599 if cherrypy.request.method == "GET":
tierno701018c2019-06-25 11:13:14 +0000600 token_info = self.authenticator.authorize()
delacruzramo01b15d32019-07-02 14:37:47 +0200601 outdata = token_info # Home page
tiernoc94c3df2018-02-09 15:38:54 +0100602 else:
603 raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value,
tierno2236d202018-05-16 19:05:16 +0200604 "Method {} not allowed for tokens".format(cherrypy.request.method))
tiernoc94c3df2018-02-09 15:38:54 +0100605
tierno701018c2019-06-25 11:13:14 +0000606 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100607
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100608 except (EngineException, AuthException) as e:
tierno701018c2019-06-25 11:13:14 +0000609 # cherrypy.log("index Exception {}".format(e))
tiernoc94c3df2018-02-09 15:38:54 +0100610 cherrypy.response.status = e.http_code.value
tierno701018c2019-06-25 11:13:14 +0000611 return self._format_out("Welcome to OSM!", token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100612
613 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200614 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200615 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200616 global __version__, version_date
617 try:
618 if cherrypy.request.method != "GET":
619 raise NbiException("Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED)
620 elif args or kwargs:
621 raise NbiException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED)
622 return __version__ + " " + version_date
623 except NbiException as e:
624 cherrypy.response.status = e.http_code.value
625 problem_details = {
626 "code": e.http_code.name,
627 "status": e.http_code.value,
628 "detail": str(e),
629 }
630 return self._format_out(problem_details, None)
631
tiernoa5035702019-07-29 08:54:42 +0000632 @staticmethod
633 def _format_login(token_info):
634 """
635 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
636 log this information
637 :param token_info: Dictionary with token content
638 :return: None
639 """
640 cherrypy.request.login = token_info.get("username", "-")
641 if token_info.get("project_name"):
642 cherrypy.request.login += "/" + token_info["project_name"]
643 if token_info.get("id"):
644 cherrypy.request.login += ";session=" + token_info["id"][0:12]
645
tierno55945e72018-04-06 16:40:27 +0200646 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100647 def token(self, method, token_id=None, kwargs=None):
tierno701018c2019-06-25 11:13:14 +0000648 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100649 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +0100650 indata = self._format_in(kwargs)
651 if not isinstance(indata, dict):
652 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST)
tiernoa5035702019-07-29 08:54:42 +0000653
654 if method == "GET":
655 token_info = self.authenticator.authorize()
656 # for logging
657 self._format_login(token_info)
658 if token_id:
659 outdata = self.authenticator.get_token(token_info, token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100660 else:
tiernoa5035702019-07-29 08:54:42 +0000661 outdata = self.authenticator.get_token_list(token_info)
662 elif method == "POST":
663 try:
664 token_info = self.authenticator.authorize()
665 except Exception:
666 token_info = None
667 if kwargs:
668 indata.update(kwargs)
669 # This is needed to log the user when authentication fails
670 cherrypy.request.login = "{}".format(indata.get("username", "-"))
671 outdata = token_info = self.authenticator.new_token(token_info, indata, cherrypy.request.remote)
672 cherrypy.session['Authorization'] = outdata["_id"]
673 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
674 # for logging
675 self._format_login(token_info)
676
677 # cherrypy.response.cookie["Authorization"] = outdata["id"]
678 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
679 elif method == "DELETE":
680 if not token_id and "id" in kwargs:
681 token_id = kwargs["id"]
682 elif not token_id:
683 token_info = self.authenticator.authorize()
684 # for logging
685 self._format_login(token_info)
686 token_id = token_info["_id"]
687 outdata = self.authenticator.del_token(token_id)
688 token_info = None
689 cherrypy.session['Authorization'] = "logout"
690 # cherrypy.response.cookie["Authorization"] = token_id
691 # cherrypy.response.cookie["Authorization"]['expires'] = 0
692 else:
693 raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
694 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100695
696 @cherrypy.expose
697 def test(self, *args, **kwargs):
698 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +0100699 if args and args[0] == "help":
tiernof717cbe2018-12-03 16:35:42 +0000700 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
tiernoe1281182018-05-22 12:24:36 +0200701 "sleep/<time>\nmessage/topic\n</pre></html>"
tiernof27c79b2018-03-12 17:08:42 +0100702
703 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +0100704 try:
705 # self.engine.load_dbase(cherrypy.request.app.config)
706 self.engine.create_admin()
707 return "Done. User 'admin', password 'admin' created"
708 except Exception:
709 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
710 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +0100711 elif args and args[0] == "file":
712 return cherrypy.lib.static.serve_file(cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1],
713 "text/plain", "attachment")
714 elif args and args[0] == "file2":
715 f_path = cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1]
716 f = open(f_path, "r")
717 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100718 return f
tierno55945e72018-04-06 16:40:27 +0200719
tiernof27c79b2018-03-12 17:08:42 +0100720 elif len(args) == 2 and args[0] == "db-clear":
tiernof717cbe2018-12-03 16:35:42 +0000721 deleted_info = self.engine.db.del_list(args[1], kwargs)
722 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
723 elif len(args) and args[0] == "fs-clear":
724 if len(args) >= 2:
725 folders = (args[1],)
726 else:
727 folders = self.engine.fs.dir_ls(".")
728 for folder in folders:
729 self.engine.fs.file_delete(folder)
730 return ",".join(folders) + " folders deleted\n"
tiernoc94c3df2018-02-09 15:38:54 +0100731 elif args and args[0] == "login":
732 if not cherrypy.request.headers.get("Authorization"):
733 cherrypy.response.headers["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
734 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
735 elif args and args[0] == "login2":
736 if not cherrypy.request.headers.get("Authorization"):
737 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
738 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
739 elif args and args[0] == "sleep":
740 sleep_time = 5
741 try:
742 sleep_time = int(args[1])
743 except Exception:
744 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
745 return self._format_out("Database already initialized")
746 thread_info = cherrypy.thread_data
747 print(thread_info)
748 time.sleep(sleep_time)
749 # thread_info
750 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +0200751 main_topic = args[1]
752 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +0100753 try:
tierno55945e72018-04-06 16:40:27 +0200754 if cherrypy.request.method == 'POST':
755 to_send = yaml.load(cherrypy.request.body)
756 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +0200757 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +0200758 return_text += " {}: {}\n".format(k, v)
759 elif cherrypy.request.method == 'GET':
760 for k, v in kwargs.items():
tiernob24258a2018-10-04 18:39:49 +0200761 self.engine.msg.write(main_topic, k, yaml.load(v))
tierno55945e72018-04-06 16:40:27 +0200762 return_text += " {}: {}\n".format(k, yaml.load(v))
tiernoc94c3df2018-02-09 15:38:54 +0100763 except Exception as e:
tierno55945e72018-04-06 16:40:27 +0200764 return_text += "Error: " + str(e)
765 return_text += "</pre></html>\n"
766 return return_text
tiernoc94c3df2018-02-09 15:38:54 +0100767
768 return_text = (
769 "<html><pre>\nheaders:\n args: {}\n".format(args) +
770 " kwargs: {}\n".format(kwargs) +
771 " headers: {}\n".format(cherrypy.request.headers) +
772 " path_info: {}\n".format(cherrypy.request.path_info) +
773 " query_string: {}\n".format(cherrypy.request.query_string) +
774 " session: {}\n".format(cherrypy.session) +
775 " cookie: {}\n".format(cherrypy.request.cookie) +
776 " method: {}\n".format(cherrypy.request.method) +
tiernof717cbe2018-12-03 16:35:42 +0000777 " session: {}\n".format(cherrypy.session.get('fieldname')) +
tiernoc94c3df2018-02-09 15:38:54 +0100778 " body:\n")
779 return_text += " length: {}\n".format(cherrypy.request.body.length)
780 if cherrypy.request.body.length:
781 return_text += " content: {}\n".format(
782 str(cherrypy.request.body.read(int(cherrypy.request.headers.get('Content-Length', 0)))))
783 if thread_info:
784 return_text += "thread: {}\n".format(thread_info)
785 return_text += "</pre></html>"
786 return return_text
787
tierno701018c2019-06-25 11:13:14 +0000788 @staticmethod
789 def _check_valid_url_method(method, *args):
tiernof27c79b2018-03-12 17:08:42 +0100790 if len(args) < 3:
tiernob24258a2018-10-04 18:39:49 +0200791 raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus.METHOD_NOT_ALLOWED)
tiernof27c79b2018-03-12 17:08:42 +0100792
tierno701018c2019-06-25 11:13:14 +0000793 reference = valid_url_methods
tiernof27c79b2018-03-12 17:08:42 +0100794 for arg in args:
795 if arg is None:
796 break
797 if not isinstance(reference, dict):
798 raise NbiException("URL contains unexpected extra items '{}'".format(arg),
799 HTTPStatus.METHOD_NOT_ALLOWED)
800
801 if arg in reference:
802 reference = reference[arg]
803 elif "<ID>" in reference:
804 reference = reference["<ID>"]
805 elif "*" in reference:
806 reference = reference["*"]
807 break
808 else:
809 raise NbiException("Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED)
810 if "TODO" in reference and method in reference["TODO"]:
811 raise NbiException("Method {} not supported yet for this URL".format(method), HTTPStatus.NOT_IMPLEMENTED)
tierno2236d202018-05-16 19:05:16 +0200812 elif "METHODS" in reference and method not in reference["METHODS"]:
tiernof27c79b2018-03-12 17:08:42 +0100813 raise NbiException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tierno701018c2019-06-25 11:13:14 +0000814 return reference["ROLE_PERMISSION"] + method.lower()
tiernof27c79b2018-03-12 17:08:42 +0100815
816 @staticmethod
tiernob24258a2018-10-04 18:39:49 +0200817 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +0100818 """
819 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +0200820 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +0100821 :param version:
tiernob24258a2018-10-04 18:39:49 +0200822 :param topic:
tiernof27c79b2018-03-12 17:08:42 +0100823 :param id:
824 :return: None
825 """
826 # 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 +0200827 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(main_topic, version, topic, id)
tiernof27c79b2018-03-12 17:08:42 +0100828 return
829
tierno65ca36d2019-02-12 19:27:52 +0100830 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000831 def _extract_query_string_operations(kwargs, method):
832 """
833
834 :param kwargs:
835 :return:
836 """
837 query_string_operations = []
838 if kwargs:
839 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
840 if qs in kwargs and kwargs[qs].lower() != "false":
841 query_string_operations.append(qs.lower() + ":" + method.lower())
842 return query_string_operations
843
844 @staticmethod
845 def _manage_admin_query(token_info, kwargs, method, _id):
tierno65ca36d2019-02-12 19:27:52 +0100846 """
847 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
848 Check that users has rights to use them and returs the admin_query
tierno701018c2019-06-25 11:13:14 +0000849 :param token_info: token_info rights obtained by token
tierno65ca36d2019-02-12 19:27:52 +0100850 :param kwargs: query string input.
851 :param method: http method: GET, POSST, PUT, ...
852 :param _id:
853 :return: admin_query dictionary with keys:
854 public: True, False or None
855 force: True or False
856 project_id: tuple with projects used for accessing an element
857 set_project: tuple with projects that a created element will belong to
858 method: show, list, delete, write
859 """
tierno701018c2019-06-25 11:13:14 +0000860 admin_query = {"force": False, "project_id": (token_info["project_id"], ), "username": token_info["username"],
861 "admin": token_info["admin"], "public": None}
tierno65ca36d2019-02-12 19:27:52 +0100862 if kwargs:
863 # FORCE
864 if "FORCE" in kwargs:
865 if kwargs["FORCE"].lower() != "false": # if None or True set force to True
866 admin_query["force"] = True
867 del kwargs["FORCE"]
868 # PUBLIC
869 if "PUBLIC" in kwargs:
870 if kwargs["PUBLIC"].lower() != "false": # if None or True set public to True
871 admin_query["public"] = True
872 else:
873 admin_query["public"] = False
874 del kwargs["PUBLIC"]
875 # ADMIN
876 if "ADMIN" in kwargs:
877 behave_as = kwargs.pop("ADMIN")
878 if behave_as.lower() != "false":
tierno701018c2019-06-25 11:13:14 +0000879 if not token_info["admin"]:
tierno65ca36d2019-02-12 19:27:52 +0100880 raise NbiException("Only admin projects can use 'ADMIN' query string", HTTPStatus.UNAUTHORIZED)
881 if not behave_as or behave_as.lower() == "true": # convert True, None to empty list
882 admin_query["project_id"] = ()
883 elif isinstance(behave_as, (list, tuple)):
884 admin_query["project_id"] = behave_as
885 else: # isinstance(behave_as, str)
886 admin_query["project_id"] = (behave_as, )
887 if "SET_PROJECT" in kwargs:
888 set_project = kwargs.pop("SET_PROJECT")
889 if not set_project:
890 admin_query["set_project"] = list(admin_query["project_id"])
891 else:
892 if isinstance(set_project, str):
893 set_project = (set_project, )
894 if admin_query["project_id"]:
895 for p in set_project:
896 if p not in admin_query["project_id"]:
897 raise NbiException("Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
898 "'ADMIN='{p}'".format(p=p), HTTPStatus.UNAUTHORIZED)
899 admin_query["set_project"] = set_project
900
901 # PROJECT_READ
902 # if "PROJECT_READ" in kwargs:
903 # admin_query["project"] = kwargs.pop("project")
tierno701018c2019-06-25 11:13:14 +0000904 # if admin_query["project"] == token_info["project_id"]:
tierno65ca36d2019-02-12 19:27:52 +0100905 if method == "GET":
906 if _id:
907 admin_query["method"] = "show"
908 else:
909 admin_query["method"] = "list"
910 elif method == "DELETE":
911 admin_query["method"] = "delete"
912 else:
913 admin_query["method"] = "write"
914 return admin_query
915
tiernoc94c3df2018-02-09 15:38:54 +0100916 @cherrypy.expose
tiernob24258a2018-10-04 18:39:49 +0200917 def default(self, main_topic=None, version=None, topic=None, _id=None, item=None, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000918 token_info = None
tiernof27c79b2018-03-12 17:08:42 +0100919 outdata = None
920 _format = None
tierno0f98af52018-03-19 10:28:22 +0100921 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +0200922 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +0200923 rollback = []
tierno701018c2019-06-25 11:13:14 +0000924 engine_session = None
tiernoc94c3df2018-02-09 15:38:54 +0100925 try:
tiernob24258a2018-10-04 18:39:49 +0200926 if not main_topic or not version or not topic:
927 raise NbiException("URL must contain at least 'main_topic/version/topic'",
928 HTTPStatus.METHOD_NOT_ALLOWED)
vijay.r35ef2f72019-04-30 17:55:49 +0530929 if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm", "nspm"):
tiernob24258a2018-10-04 18:39:49 +0200930 raise NbiException("URL main_topic '{}' not supported".format(main_topic),
931 HTTPStatus.METHOD_NOT_ALLOWED)
tiernoc94c3df2018-02-09 15:38:54 +0100932 if version != 'v1':
933 raise NbiException("URL version '{}' not supported".format(version), HTTPStatus.METHOD_NOT_ALLOWED)
934
tiernof27c79b2018-03-12 17:08:42 +0100935 if kwargs and "METHOD" in kwargs and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
936 method = kwargs.pop("METHOD")
937 else:
938 method = cherrypy.request.method
tierno65ca36d2019-02-12 19:27:52 +0100939
tierno701018c2019-06-25 11:13:14 +0000940 role_permission = self._check_valid_url_method(method, main_topic, version, topic, _id, item, *args)
941 query_string_operations = self._extract_query_string_operations(kwargs, method)
tiernob24258a2018-10-04 18:39:49 +0200942 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +0100943 return self.token(method, _id, kwargs)
944
tierno701018c2019-06-25 11:13:14 +0000945 token_info = self.authenticator.authorize(role_permission, query_string_operations)
946 engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
tiernof27c79b2018-03-12 17:08:42 +0100947 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +0200948 engine_topic = topic
949 if topic == "subscriptions":
950 engine_topic = main_topic + "_" + topic
vijay.r35ef2f72019-04-30 17:55:49 +0530951 if item and topic != "pm_jobs":
tiernob24258a2018-10-04 18:39:49 +0200952 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +0100953
tiernob24258a2018-10-04 18:39:49 +0200954 if main_topic == "nsd":
955 engine_topic = "nsds"
956 elif main_topic == "vnfpkgm":
957 engine_topic = "vnfds"
958 elif main_topic == "nslcm":
959 engine_topic = "nsrs"
960 if topic == "ns_lcm_op_occs":
961 engine_topic = "nslcmops"
962 if topic == "vnfrs" or topic == "vnf_instances":
963 engine_topic = "vnfrs"
garciadeblas9750c5a2018-10-15 16:20:35 +0200964 elif main_topic == "nst":
965 engine_topic = "nsts"
966 elif main_topic == "nsilcm":
967 engine_topic = "nsis"
968 if topic == "nsi_lcm_op_occs":
delacruzramoc061f562019-04-05 11:00:02 +0200969 engine_topic = "nsilcmops"
tiernob24258a2018-10-04 18:39:49 +0200970 elif main_topic == "pdu":
971 engine_topic = "pdus"
tierno65ca36d2019-02-12 19:27:52 +0100972 if engine_topic == "vims": # TODO this is for backward compatibility, it will be removed in the future
tiernob24258a2018-10-04 18:39:49 +0200973 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +0100974
975 if method == "GET":
Felipe Vicens07f31722018-10-29 15:16:44 +0100976 if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
garciadeblas9750c5a2018-10-15 16:20:35 +0200977 if item in ("vnfd", "nsd", "nst"):
tiernof27c79b2018-03-12 17:08:42 +0100978 path = "$DESCRIPTOR"
979 elif args:
980 path = args
tiernob24258a2018-10-04 18:39:49 +0200981 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +0100982 path = ()
983 else:
984 path = None
tierno701018c2019-06-25 11:13:14 +0000985 file, _format = self.engine.get_file(engine_session, engine_topic, _id, path,
tierno2236d202018-05-16 19:05:16 +0200986 cherrypy.request.headers.get("Accept"))
tiernof27c79b2018-03-12 17:08:42 +0100987 outdata = file
988 elif not _id:
tierno701018c2019-06-25 11:13:14 +0000989 outdata = self.engine.get_item_list(engine_session, engine_topic, kwargs)
tiernoc94c3df2018-02-09 15:38:54 +0100990 else:
vijay.r35ef2f72019-04-30 17:55:49 +0530991 if item == "reports":
992 # TODO check that project_id (_id in this context) has permissions
993 _id = args[0]
tierno701018c2019-06-25 11:13:14 +0000994 outdata = self.engine.get_item(engine_session, engine_topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100995 elif method == "POST":
tiernobdebce92019-07-01 15:36:49 +0000996 cherrypy.response.status = HTTPStatus.CREATED.value
garciadeblas9750c5a2018-10-15 16:20:35 +0200997 if topic in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"):
tiernof27c79b2018-03-12 17:08:42 +0100998 _id = cherrypy.request.headers.get("Transaction-Id")
999 if not _id:
tiernobdebce92019-07-01 15:36:49 +00001000 _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, {}, None,
1001 cherrypy.request.headers)
tierno701018c2019-06-25 11:13:14 +00001002 completed = self.engine.upload_content(engine_session, engine_topic, _id, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +01001003 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +01001004 if completed:
tiernob24258a2018-10-04 18:39:49 +02001005 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001006 else:
1007 cherrypy.response.headers["Transaction-Id"] = _id
1008 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +02001009 elif topic == "ns_instances_content":
1010 # creates NSR
tiernobdebce92019-07-01 15:36:49 +00001011 _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs)
tiernob24258a2018-10-04 18:39:49 +02001012 # creates nslcmop
1013 indata["lcmOperationType"] = "instantiate"
1014 indata["nsInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001015 nslcmop_id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", indata, None)
tiernob24258a2018-10-04 18:39:49 +02001016 self._set_location_header(main_topic, version, topic, _id)
kuuse078f55e2019-05-16 19:24:21 +02001017 outdata = {"id": _id, "nslcmop_id": nslcmop_id}
tiernob24258a2018-10-04 18:39:49 +02001018 elif topic == "ns_instances" and item:
1019 indata["lcmOperationType"] = item
1020 indata["nsInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001021 _id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", indata, kwargs)
tiernob24258a2018-10-04 18:39:49 +02001022 self._set_location_header(main_topic, version, "ns_lcm_op_occs", _id)
tierno65acb4d2018-04-06 16:42:40 +02001023 outdata = {"id": _id}
1024 cherrypy.response.status = HTTPStatus.ACCEPTED.value
garciadeblas9750c5a2018-10-15 16:20:35 +02001025 elif topic == "netslice_instances_content":
Felipe Vicens07f31722018-10-29 15:16:44 +01001026 # creates NetSlice_Instance_record (NSIR)
tiernobdebce92019-07-01 15:36:49 +00001027 _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs)
Felipe Vicens07f31722018-10-29 15:16:44 +01001028 self._set_location_header(main_topic, version, topic, _id)
garciadeblas9750c5a2018-10-15 16:20:35 +02001029 indata["lcmOperationType"] = "instantiate"
Felipe Vicens126af572019-06-05 19:13:04 +02001030 indata["netsliceInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001031 nsilcmop_id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", indata, kwargs)
kuuse078f55e2019-05-16 19:24:21 +02001032 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
delacruzramoc061f562019-04-05 11:00:02 +02001033
garciadeblas9750c5a2018-10-15 16:20:35 +02001034 elif topic == "netslice_instances" and item:
1035 indata["lcmOperationType"] = item
Felipe Vicens126af572019-06-05 19:13:04 +02001036 indata["netsliceInstanceId"] = _id
tiernobdebce92019-07-01 15:36:49 +00001037 _id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", indata, kwargs)
garciadeblas9750c5a2018-10-15 16:20:35 +02001038 self._set_location_header(main_topic, version, "nsi_lcm_op_occs", _id)
1039 outdata = {"id": _id}
1040 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +01001041 else:
tiernobdebce92019-07-01 15:36:49 +00001042 _id, op_id = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs,
1043 cherrypy.request.headers)
tiernob24258a2018-10-04 18:39:49 +02001044 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001045 outdata = {"id": _id}
tiernobdebce92019-07-01 15:36:49 +00001046 if op_id:
1047 outdata["op_id"] = op_id
1048 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernob24258a2018-10-04 18:39:49 +02001049 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tierno09c073e2018-04-26 13:36:48 +02001050
tiernoc94c3df2018-02-09 15:38:54 +01001051 elif method == "DELETE":
1052 if not _id:
tierno701018c2019-06-25 11:13:14 +00001053 outdata = self.engine.del_item_list(engine_session, engine_topic, kwargs)
tierno09c073e2018-04-26 13:36:48 +02001054 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +01001055 else: # len(args) > 1
tiernoe8631782018-12-21 13:31:52 +00001056 delete_in_process = False
tierno701018c2019-06-25 11:13:14 +00001057 if topic == "ns_instances_content" and not engine_session["force"]:
tiernob24258a2018-10-04 18:39:49 +02001058 nslcmop_desc = {
1059 "lcmOperationType": "terminate",
1060 "nsInstanceId": _id,
1061 "autoremove": True
1062 }
tiernobdebce92019-07-01 15:36:49 +00001063 opp_id, _ = self.engine.new_item(rollback, engine_session, "nslcmops", nslcmop_desc, None)
tiernoe8631782018-12-21 13:31:52 +00001064 if opp_id:
1065 delete_in_process = True
1066 outdata = {"_id": opp_id}
1067 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tierno701018c2019-06-25 11:13:14 +00001068 elif topic == "netslice_instances_content" and not engine_session["force"]:
garciadeblas9750c5a2018-10-15 16:20:35 +02001069 nsilcmop_desc = {
1070 "lcmOperationType": "terminate",
Felipe Vicens126af572019-06-05 19:13:04 +02001071 "netsliceInstanceId": _id,
garciadeblas9750c5a2018-10-15 16:20:35 +02001072 "autoremove": True
1073 }
tiernobdebce92019-07-01 15:36:49 +00001074 opp_id, _ = self.engine.new_item(rollback, engine_session, "nsilcmops", nsilcmop_desc, None)
tiernoe8631782018-12-21 13:31:52 +00001075 if opp_id:
1076 delete_in_process = True
1077 outdata = {"_id": opp_id}
1078 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1079 if not delete_in_process:
tierno701018c2019-06-25 11:13:14 +00001080 self.engine.del_item(engine_session, engine_topic, _id)
tierno09c073e2018-04-26 13:36:48 +02001081 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tierno55ba2e62018-12-11 17:22:22 +00001082 if engine_topic in ("vim_accounts", "wim_accounts", "sdns"):
tierno09c073e2018-04-26 13:36:48 +02001083 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1084
tierno7ae10112018-05-18 14:36:02 +02001085 elif method in ("PUT", "PATCH"):
tiernobdebce92019-07-01 15:36:49 +00001086 op_id = None
tierno701018c2019-06-25 11:13:14 +00001087 if not indata and not kwargs and not engine_session.get("set_project"):
tiernoc94c3df2018-02-09 15:38:54 +01001088 raise NbiException("Nothing to update. Provide payload and/or query string",
1089 HTTPStatus.BAD_REQUEST)
garciadeblas9750c5a2018-10-15 16:20:35 +02001090 if item in ("nsd_content", "package_content", "nst_content") and method == "PUT":
tierno701018c2019-06-25 11:13:14 +00001091 completed = self.engine.upload_content(engine_session, engine_topic, _id, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +01001092 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +01001093 if not completed:
1094 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +01001095 else:
tiernobdebce92019-07-01 15:36:49 +00001096 op_id = self.engine.edit_item(engine_session, engine_topic, _id, indata, kwargs)
1097
1098 if op_id:
1099 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1100 outdata = {"op_id": op_id}
1101 else:
1102 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
1103 outdata = None
tiernoc94c3df2018-02-09 15:38:54 +01001104 else:
1105 raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tiernoa6bb45d2019-06-14 09:45:39 +00001106
1107 # if Role information changes, it is needed to reload the information of roles
1108 if topic == "roles" and method != "GET":
1109 self.authenticator.load_operation_to_allowed_roles()
tierno701018c2019-06-25 11:13:14 +00001110 return self._format_out(outdata, token_info, _format)
tiernob24258a2018-10-04 18:39:49 +02001111 except Exception as e:
tierno36ec8602018-11-02 17:27:11 +01001112 if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException,
delacruzramo01b15d32019-07-02 14:37:47 +02001113 ValidationError, AuthconnException)):
tiernob24258a2018-10-04 18:39:49 +02001114 http_code_value = cherrypy.response.status = e.http_code.value
1115 http_code_name = e.http_code.name
1116 cherrypy.log("Exception {}".format(e))
1117 else:
1118 http_code_value = cherrypy.response.status = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Felipe Vicense36ab852018-11-23 14:12:09 +01001119 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
tiernob24258a2018-10-04 18:39:49 +02001120 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +02001121 if hasattr(outdata, "close"): # is an open file
1122 outdata.close()
tiernob24258a2018-10-04 18:39:49 +02001123 error_text = str(e)
1124 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +02001125 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +02001126 try:
tiernocc103432018-10-19 14:10:35 +02001127 if rollback_item.get("operation") == "set":
1128 self.engine.db.set_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
1129 rollback_item["content"], fail_on_empty=False)
1130 else:
tiernoe8631782018-12-21 13:31:52 +00001131 self.engine.db.del_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
1132 fail_on_empty=False)
tierno3ace63c2018-05-03 17:51:43 +02001133 except Exception as e2:
tiernob24258a2018-10-04 18:39:49 +02001134 rollback_error_text = "Rollback Exception {}: {}".format(rollback_item, e2)
1135 cherrypy.log(rollback_error_text)
1136 error_text += ". " + rollback_error_text
1137 # if isinstance(e, MsgException):
1138 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1139 # engine_topic[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +01001140 problem_details = {
tiernob24258a2018-10-04 18:39:49 +02001141 "code": http_code_name,
1142 "status": http_code_value,
1143 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +01001144 }
tierno701018c2019-06-25 11:13:14 +00001145 return self._format_out(problem_details, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001146 # raise cherrypy.HTTPError(e.http_code.value, str(e))
tiernoa5035702019-07-29 08:54:42 +00001147 finally:
1148 if token_info:
1149 self._format_login(token_info)
1150 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
1151 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1152 if outdata.get(logging_id):
1153 cherrypy.request.login += ";{}={}".format(logging_id, outdata[logging_id][:36])
tiernoc94c3df2018-02-09 15:38:54 +01001154
1155
tiernoc94c3df2018-02-09 15:38:54 +01001156def _start_service():
1157 """
1158 Callback function called when cherrypy.engine starts
1159 Override configuration with env variables
1160 Set database, storage, message configuration
1161 Init database with admin/admin user password
1162 """
tierno932499c2019-01-28 17:28:10 +00001163 global nbi_server
1164 global subscription_thread
tiernoc94c3df2018-02-09 15:38:54 +01001165 cherrypy.log.error("Starting osm_nbi")
1166 # update general cherrypy configuration
1167 update_dict = {}
1168
1169 engine_config = cherrypy.tree.apps['/osm'].config
1170 for k, v in environ.items():
1171 if not k.startswith("OSMNBI_"):
1172 continue
tiernoe1281182018-05-22 12:24:36 +02001173 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +01001174 if not k2:
1175 continue
1176 try:
1177 # update static configuration
1178 if k == 'OSMNBI_STATIC_DIR':
1179 engine_config["/static"]['tools.staticdir.dir'] = v
1180 engine_config["/static"]['tools.staticdir.on'] = True
1181 elif k == 'OSMNBI_SOCKET_PORT' or k == 'OSMNBI_SERVER_PORT':
1182 update_dict['server.socket_port'] = int(v)
1183 elif k == 'OSMNBI_SOCKET_HOST' or k == 'OSMNBI_SERVER_HOST':
1184 update_dict['server.socket_host'] = v
tiernof5298be2018-05-16 14:43:57 +02001185 elif k1 in ("server", "test", "auth", "log"):
1186 update_dict[k1 + '.' + k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001187 elif k1 in ("message", "database", "storage", "authentication"):
tiernof5298be2018-05-16 14:43:57 +02001188 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001189 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +01001190 engine_config[k1][k2] = int(v)
1191 else:
1192 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001193
tiernoc94c3df2018-02-09 15:38:54 +01001194 except ValueError as e:
1195 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1196 except Exception as e:
1197 cherrypy.log.warn("skipping environ '{}' on exception '{}'".format(k, e))
1198
1199 if update_dict:
1200 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +02001201 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001202
1203 # logging cherrypy
1204 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1205 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
1206 logger_server = logging.getLogger("cherrypy.error")
1207 logger_access = logging.getLogger("cherrypy.access")
1208 logger_cherry = logging.getLogger("cherrypy")
1209 logger_nbi = logging.getLogger("nbi")
1210
tiernof5298be2018-05-16 14:43:57 +02001211 if "log.file" in engine_config["global"]:
1212 file_handler = logging.handlers.RotatingFileHandler(engine_config["global"]["log.file"],
tiernoc94c3df2018-02-09 15:38:54 +01001213 maxBytes=100e6, backupCount=9, delay=0)
1214 file_handler.setFormatter(log_formatter_simple)
1215 logger_cherry.addHandler(file_handler)
1216 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +02001217 # log always to standard output
1218 for format_, logger in {"nbi.server %(filename)s:%(lineno)s": logger_server,
1219 "nbi.access %(filename)s:%(lineno)s": logger_access,
1220 "%(name)s %(filename)s:%(lineno)s": logger_nbi
1221 }.items():
1222 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
1223 log_formatter_cherry = logging.Formatter(log_format_cherry, datefmt='%Y-%m-%dT%H:%M:%S')
1224 str_handler = logging.StreamHandler()
1225 str_handler.setFormatter(log_formatter_cherry)
1226 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +01001227
tiernof5298be2018-05-16 14:43:57 +02001228 if engine_config["global"].get("log.level"):
1229 logger_cherry.setLevel(engine_config["global"]["log.level"])
1230 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +01001231
1232 # logging other modules
1233 for k1, logname in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
1234 engine_config[k1]["logger_name"] = logname
1235 logger_module = logging.getLogger(logname)
1236 if "logfile" in engine_config[k1]:
1237 file_handler = logging.handlers.RotatingFileHandler(engine_config[k1]["logfile"],
tierno2236d202018-05-16 19:05:16 +02001238 maxBytes=100e6, backupCount=9, delay=0)
tiernoc94c3df2018-02-09 15:38:54 +01001239 file_handler.setFormatter(log_formatter_simple)
1240 logger_module.addHandler(file_handler)
1241 if "loglevel" in engine_config[k1]:
1242 logger_module.setLevel(engine_config[k1]["loglevel"])
1243 # TODO add more entries, e.g.: storage
1244 cherrypy.tree.apps['/osm'].root.engine.start(engine_config)
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001245 cherrypy.tree.apps['/osm'].root.authenticator.start(engine_config)
tiernod985a8d2018-10-19 14:12:28 +02001246 cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version)
1247 cherrypy.tree.apps['/osm'].root.authenticator.init_db(target_version=auth_database_version)
tiernobee508e2019-01-21 11:21:49 +00001248
tierno932499c2019-01-28 17:28:10 +00001249 # start subscriptions thread:
1250 subscription_thread = SubscriptionThread(config=engine_config, engine=nbi_server.engine)
1251 subscription_thread.start()
1252 # Do not capture except SubscriptionException
1253
tiernobee508e2019-01-21 11:21:49 +00001254 # load and print version. Ignore possible errors, e.g. file not found
1255 try:
1256 with open("{}/version".format(engine_config["/static"]['tools.staticdir.dir'])) as version_file:
1257 version_data = version_file.read()
delacruzramo01b15d32019-07-02 14:37:47 +02001258 version = version_data.replace("\n", " ")
1259 backend = engine_config["authentication"]["backend"]
1260 cherrypy.log.error("Starting OSM NBI Version {} with {} authentication backend"
1261 .format(version, 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)