blob: 9188834ff0d674c3a3fdd3124c1f951869b01f57 [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
Eduardo Sousa819d34c2018-07-31 01:20:02 +010027from authconn import AuthException
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()
tiernoc94c3df2018-02-09 15:38:54 +0100601 outdata = "Index page"
602 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
632 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100633 def token(self, method, token_id=None, kwargs=None):
tierno701018c2019-06-25 11:13:14 +0000634 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100635 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +0100636 indata = self._format_in(kwargs)
637 if not isinstance(indata, dict):
638 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100639 try:
tiernoc94c3df2018-02-09 15:38:54 +0100640 if method == "GET":
tierno701018c2019-06-25 11:13:14 +0000641 token_info = self.authenticator.authorize()
tiernof27c79b2018-03-12 17:08:42 +0100642 if token_id:
tierno701018c2019-06-25 11:13:14 +0000643 outdata = self.authenticator.get_token(token_info, token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100644 else:
tierno701018c2019-06-25 11:13:14 +0000645 outdata = self.authenticator.get_token_list(token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100646 elif method == "POST":
647 try:
tierno701018c2019-06-25 11:13:14 +0000648 token_info = self.authenticator.authorize()
tiernoe1281182018-05-22 12:24:36 +0200649 except Exception:
tierno701018c2019-06-25 11:13:14 +0000650 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100651 if kwargs:
652 indata.update(kwargs)
tierno701018c2019-06-25 11:13:14 +0000653 outdata = self.authenticator.new_token(token_info, indata, cherrypy.request.remote)
654 token_info = outdata
tiernoc94c3df2018-02-09 15:38:54 +0100655 cherrypy.session['Authorization'] = outdata["_id"]
tierno0f98af52018-03-19 10:28:22 +0100656 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
tiernoc94c3df2018-02-09 15:38:54 +0100657 # cherrypy.response.cookie["Authorization"] = outdata["id"]
658 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
659 elif method == "DELETE":
tiernof27c79b2018-03-12 17:08:42 +0100660 if not token_id and "id" in kwargs:
tiernoc94c3df2018-02-09 15:38:54 +0100661 token_id = kwargs["id"]
tiernof27c79b2018-03-12 17:08:42 +0100662 elif not token_id:
tierno701018c2019-06-25 11:13:14 +0000663 token_info = self.authenticator.authorize()
664 token_id = token_info["_id"]
Eduardo Sousa2f988212018-07-26 01:04:11 +0100665 outdata = self.authenticator.del_token(token_id)
tierno701018c2019-06-25 11:13:14 +0000666 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100667 cherrypy.session['Authorization'] = "logout"
668 # cherrypy.response.cookie["Authorization"] = token_id
669 # cherrypy.response.cookie["Authorization"]['expires'] = 0
670 else:
671 raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tierno701018c2019-06-25 11:13:14 +0000672 return self._format_out(outdata, token_info)
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100673 except (NbiException, EngineException, DbException, AuthException) as e:
tiernoc94c3df2018-02-09 15:38:54 +0100674 cherrypy.log("tokens Exception {}".format(e))
675 cherrypy.response.status = e.http_code.value
676 problem_details = {
677 "code": e.http_code.name,
678 "status": e.http_code.value,
679 "detail": str(e),
680 }
tierno701018c2019-06-25 11:13:14 +0000681 return self._format_out(problem_details, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100682
683 @cherrypy.expose
684 def test(self, *args, **kwargs):
685 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +0100686 if args and args[0] == "help":
tiernof717cbe2018-12-03 16:35:42 +0000687 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
tiernoe1281182018-05-22 12:24:36 +0200688 "sleep/<time>\nmessage/topic\n</pre></html>"
tiernof27c79b2018-03-12 17:08:42 +0100689
690 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +0100691 try:
692 # self.engine.load_dbase(cherrypy.request.app.config)
693 self.engine.create_admin()
694 return "Done. User 'admin', password 'admin' created"
695 except Exception:
696 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
697 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +0100698 elif args and args[0] == "file":
699 return cherrypy.lib.static.serve_file(cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1],
700 "text/plain", "attachment")
701 elif args and args[0] == "file2":
702 f_path = cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1]
703 f = open(f_path, "r")
704 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100705 return f
tierno55945e72018-04-06 16:40:27 +0200706
tiernof27c79b2018-03-12 17:08:42 +0100707 elif len(args) == 2 and args[0] == "db-clear":
tiernof717cbe2018-12-03 16:35:42 +0000708 deleted_info = self.engine.db.del_list(args[1], kwargs)
709 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
710 elif len(args) and args[0] == "fs-clear":
711 if len(args) >= 2:
712 folders = (args[1],)
713 else:
714 folders = self.engine.fs.dir_ls(".")
715 for folder in folders:
716 self.engine.fs.file_delete(folder)
717 return ",".join(folders) + " folders deleted\n"
tiernoc94c3df2018-02-09 15:38:54 +0100718 elif args and args[0] == "login":
719 if not cherrypy.request.headers.get("Authorization"):
720 cherrypy.response.headers["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
721 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
722 elif args and args[0] == "login2":
723 if not cherrypy.request.headers.get("Authorization"):
724 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
725 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
726 elif args and args[0] == "sleep":
727 sleep_time = 5
728 try:
729 sleep_time = int(args[1])
730 except Exception:
731 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
732 return self._format_out("Database already initialized")
733 thread_info = cherrypy.thread_data
734 print(thread_info)
735 time.sleep(sleep_time)
736 # thread_info
737 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +0200738 main_topic = args[1]
739 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +0100740 try:
tierno55945e72018-04-06 16:40:27 +0200741 if cherrypy.request.method == 'POST':
742 to_send = yaml.load(cherrypy.request.body)
743 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +0200744 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +0200745 return_text += " {}: {}\n".format(k, v)
746 elif cherrypy.request.method == 'GET':
747 for k, v in kwargs.items():
tiernob24258a2018-10-04 18:39:49 +0200748 self.engine.msg.write(main_topic, k, yaml.load(v))
tierno55945e72018-04-06 16:40:27 +0200749 return_text += " {}: {}\n".format(k, yaml.load(v))
tiernoc94c3df2018-02-09 15:38:54 +0100750 except Exception as e:
tierno55945e72018-04-06 16:40:27 +0200751 return_text += "Error: " + str(e)
752 return_text += "</pre></html>\n"
753 return return_text
tiernoc94c3df2018-02-09 15:38:54 +0100754
755 return_text = (
756 "<html><pre>\nheaders:\n args: {}\n".format(args) +
757 " kwargs: {}\n".format(kwargs) +
758 " headers: {}\n".format(cherrypy.request.headers) +
759 " path_info: {}\n".format(cherrypy.request.path_info) +
760 " query_string: {}\n".format(cherrypy.request.query_string) +
761 " session: {}\n".format(cherrypy.session) +
762 " cookie: {}\n".format(cherrypy.request.cookie) +
763 " method: {}\n".format(cherrypy.request.method) +
tiernof717cbe2018-12-03 16:35:42 +0000764 " session: {}\n".format(cherrypy.session.get('fieldname')) +
tiernoc94c3df2018-02-09 15:38:54 +0100765 " body:\n")
766 return_text += " length: {}\n".format(cherrypy.request.body.length)
767 if cherrypy.request.body.length:
768 return_text += " content: {}\n".format(
769 str(cherrypy.request.body.read(int(cherrypy.request.headers.get('Content-Length', 0)))))
770 if thread_info:
771 return_text += "thread: {}\n".format(thread_info)
772 return_text += "</pre></html>"
773 return return_text
774
tierno701018c2019-06-25 11:13:14 +0000775 @staticmethod
776 def _check_valid_url_method(method, *args):
tiernof27c79b2018-03-12 17:08:42 +0100777 if len(args) < 3:
tiernob24258a2018-10-04 18:39:49 +0200778 raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus.METHOD_NOT_ALLOWED)
tiernof27c79b2018-03-12 17:08:42 +0100779
tierno701018c2019-06-25 11:13:14 +0000780 reference = valid_url_methods
tiernof27c79b2018-03-12 17:08:42 +0100781 for arg in args:
782 if arg is None:
783 break
784 if not isinstance(reference, dict):
785 raise NbiException("URL contains unexpected extra items '{}'".format(arg),
786 HTTPStatus.METHOD_NOT_ALLOWED)
787
788 if arg in reference:
789 reference = reference[arg]
790 elif "<ID>" in reference:
791 reference = reference["<ID>"]
792 elif "*" in reference:
793 reference = reference["*"]
794 break
795 else:
796 raise NbiException("Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED)
797 if "TODO" in reference and method in reference["TODO"]:
798 raise NbiException("Method {} not supported yet for this URL".format(method), HTTPStatus.NOT_IMPLEMENTED)
tierno2236d202018-05-16 19:05:16 +0200799 elif "METHODS" in reference and method not in reference["METHODS"]:
tiernof27c79b2018-03-12 17:08:42 +0100800 raise NbiException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tierno701018c2019-06-25 11:13:14 +0000801 return reference["ROLE_PERMISSION"] + method.lower()
tiernof27c79b2018-03-12 17:08:42 +0100802
803 @staticmethod
tiernob24258a2018-10-04 18:39:49 +0200804 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +0100805 """
806 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +0200807 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +0100808 :param version:
tiernob24258a2018-10-04 18:39:49 +0200809 :param topic:
tiernof27c79b2018-03-12 17:08:42 +0100810 :param id:
811 :return: None
812 """
813 # 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 +0200814 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(main_topic, version, topic, id)
tiernof27c79b2018-03-12 17:08:42 +0100815 return
816
tierno65ca36d2019-02-12 19:27:52 +0100817 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000818 def _extract_query_string_operations(kwargs, method):
819 """
820
821 :param kwargs:
822 :return:
823 """
824 query_string_operations = []
825 if kwargs:
826 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
827 if qs in kwargs and kwargs[qs].lower() != "false":
828 query_string_operations.append(qs.lower() + ":" + method.lower())
829 return query_string_operations
830
831 @staticmethod
832 def _manage_admin_query(token_info, kwargs, method, _id):
tierno65ca36d2019-02-12 19:27:52 +0100833 """
834 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
835 Check that users has rights to use them and returs the admin_query
tierno701018c2019-06-25 11:13:14 +0000836 :param token_info: token_info rights obtained by token
tierno65ca36d2019-02-12 19:27:52 +0100837 :param kwargs: query string input.
838 :param method: http method: GET, POSST, PUT, ...
839 :param _id:
840 :return: admin_query dictionary with keys:
841 public: True, False or None
842 force: True or False
843 project_id: tuple with projects used for accessing an element
844 set_project: tuple with projects that a created element will belong to
845 method: show, list, delete, write
846 """
tierno701018c2019-06-25 11:13:14 +0000847 admin_query = {"force": False, "project_id": (token_info["project_id"], ), "username": token_info["username"],
848 "admin": token_info["admin"], "public": None}
tierno65ca36d2019-02-12 19:27:52 +0100849 if kwargs:
850 # FORCE
851 if "FORCE" in kwargs:
852 if kwargs["FORCE"].lower() != "false": # if None or True set force to True
853 admin_query["force"] = True
854 del kwargs["FORCE"]
855 # PUBLIC
856 if "PUBLIC" in kwargs:
857 if kwargs["PUBLIC"].lower() != "false": # if None or True set public to True
858 admin_query["public"] = True
859 else:
860 admin_query["public"] = False
861 del kwargs["PUBLIC"]
862 # ADMIN
863 if "ADMIN" in kwargs:
864 behave_as = kwargs.pop("ADMIN")
865 if behave_as.lower() != "false":
tierno701018c2019-06-25 11:13:14 +0000866 if not token_info["admin"]:
tierno65ca36d2019-02-12 19:27:52 +0100867 raise NbiException("Only admin projects can use 'ADMIN' query string", HTTPStatus.UNAUTHORIZED)
868 if not behave_as or behave_as.lower() == "true": # convert True, None to empty list
869 admin_query["project_id"] = ()
870 elif isinstance(behave_as, (list, tuple)):
871 admin_query["project_id"] = behave_as
872 else: # isinstance(behave_as, str)
873 admin_query["project_id"] = (behave_as, )
874 if "SET_PROJECT" in kwargs:
875 set_project = kwargs.pop("SET_PROJECT")
876 if not set_project:
877 admin_query["set_project"] = list(admin_query["project_id"])
878 else:
879 if isinstance(set_project, str):
880 set_project = (set_project, )
881 if admin_query["project_id"]:
882 for p in set_project:
883 if p not in admin_query["project_id"]:
884 raise NbiException("Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
885 "'ADMIN='{p}'".format(p=p), HTTPStatus.UNAUTHORIZED)
886 admin_query["set_project"] = set_project
887
888 # PROJECT_READ
889 # if "PROJECT_READ" in kwargs:
890 # admin_query["project"] = kwargs.pop("project")
tierno701018c2019-06-25 11:13:14 +0000891 # if admin_query["project"] == token_info["project_id"]:
tierno65ca36d2019-02-12 19:27:52 +0100892 if method == "GET":
893 if _id:
894 admin_query["method"] = "show"
895 else:
896 admin_query["method"] = "list"
897 elif method == "DELETE":
898 admin_query["method"] = "delete"
899 else:
900 admin_query["method"] = "write"
901 return admin_query
902
tiernoc94c3df2018-02-09 15:38:54 +0100903 @cherrypy.expose
tiernob24258a2018-10-04 18:39:49 +0200904 def default(self, main_topic=None, version=None, topic=None, _id=None, item=None, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000905 token_info = None
tiernof27c79b2018-03-12 17:08:42 +0100906 outdata = None
907 _format = None
tierno0f98af52018-03-19 10:28:22 +0100908 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +0200909 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +0200910 rollback = []
tierno701018c2019-06-25 11:13:14 +0000911 engine_session = None
tiernoc94c3df2018-02-09 15:38:54 +0100912 try:
tiernob24258a2018-10-04 18:39:49 +0200913 if not main_topic or not version or not topic:
914 raise NbiException("URL must contain at least 'main_topic/version/topic'",
915 HTTPStatus.METHOD_NOT_ALLOWED)
vijay.r35ef2f72019-04-30 17:55:49 +0530916 if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm", "nspm"):
tiernob24258a2018-10-04 18:39:49 +0200917 raise NbiException("URL main_topic '{}' not supported".format(main_topic),
918 HTTPStatus.METHOD_NOT_ALLOWED)
tiernoc94c3df2018-02-09 15:38:54 +0100919 if version != 'v1':
920 raise NbiException("URL version '{}' not supported".format(version), HTTPStatus.METHOD_NOT_ALLOWED)
921
tiernof27c79b2018-03-12 17:08:42 +0100922 if kwargs and "METHOD" in kwargs and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
923 method = kwargs.pop("METHOD")
924 else:
925 method = cherrypy.request.method
tierno65ca36d2019-02-12 19:27:52 +0100926
tierno701018c2019-06-25 11:13:14 +0000927 role_permission = self._check_valid_url_method(method, main_topic, version, topic, _id, item, *args)
928 query_string_operations = self._extract_query_string_operations(kwargs, method)
tiernob24258a2018-10-04 18:39:49 +0200929 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +0100930 return self.token(method, _id, kwargs)
931
tiernoc94c3df2018-02-09 15:38:54 +0100932 # self.engine.load_dbase(cherrypy.request.app.config)
tierno701018c2019-06-25 11:13:14 +0000933
934 token_info = self.authenticator.authorize(role_permission, query_string_operations)
935 engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
tiernof27c79b2018-03-12 17:08:42 +0100936 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +0200937 engine_topic = topic
938 if topic == "subscriptions":
939 engine_topic = main_topic + "_" + topic
vijay.r35ef2f72019-04-30 17:55:49 +0530940 if item and topic != "pm_jobs":
tiernob24258a2018-10-04 18:39:49 +0200941 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +0100942
tiernob24258a2018-10-04 18:39:49 +0200943 if main_topic == "nsd":
944 engine_topic = "nsds"
945 elif main_topic == "vnfpkgm":
946 engine_topic = "vnfds"
947 elif main_topic == "nslcm":
948 engine_topic = "nsrs"
949 if topic == "ns_lcm_op_occs":
950 engine_topic = "nslcmops"
951 if topic == "vnfrs" or topic == "vnf_instances":
952 engine_topic = "vnfrs"
garciadeblas9750c5a2018-10-15 16:20:35 +0200953 elif main_topic == "nst":
954 engine_topic = "nsts"
955 elif main_topic == "nsilcm":
956 engine_topic = "nsis"
957 if topic == "nsi_lcm_op_occs":
delacruzramoc061f562019-04-05 11:00:02 +0200958 engine_topic = "nsilcmops"
tiernob24258a2018-10-04 18:39:49 +0200959 elif main_topic == "pdu":
960 engine_topic = "pdus"
tierno65ca36d2019-02-12 19:27:52 +0100961 if engine_topic == "vims": # TODO this is for backward compatibility, it will be removed in the future
tiernob24258a2018-10-04 18:39:49 +0200962 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +0100963
964 if method == "GET":
Felipe Vicens07f31722018-10-29 15:16:44 +0100965 if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
garciadeblas9750c5a2018-10-15 16:20:35 +0200966 if item in ("vnfd", "nsd", "nst"):
tiernof27c79b2018-03-12 17:08:42 +0100967 path = "$DESCRIPTOR"
968 elif args:
969 path = args
tiernob24258a2018-10-04 18:39:49 +0200970 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +0100971 path = ()
972 else:
973 path = None
tierno701018c2019-06-25 11:13:14 +0000974 file, _format = self.engine.get_file(engine_session, engine_topic, _id, path,
tierno2236d202018-05-16 19:05:16 +0200975 cherrypy.request.headers.get("Accept"))
tiernof27c79b2018-03-12 17:08:42 +0100976 outdata = file
977 elif not _id:
tierno701018c2019-06-25 11:13:14 +0000978 outdata = self.engine.get_item_list(engine_session, engine_topic, kwargs)
tiernoc94c3df2018-02-09 15:38:54 +0100979 else:
vijay.r35ef2f72019-04-30 17:55:49 +0530980 if item == "reports":
981 # TODO check that project_id (_id in this context) has permissions
982 _id = args[0]
tierno701018c2019-06-25 11:13:14 +0000983 outdata = self.engine.get_item(engine_session, engine_topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100984 elif method == "POST":
garciadeblas9750c5a2018-10-15 16:20:35 +0200985 if topic in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"):
tiernof27c79b2018-03-12 17:08:42 +0100986 _id = cherrypy.request.headers.get("Transaction-Id")
987 if not _id:
tierno701018c2019-06-25 11:13:14 +0000988 _id = self.engine.new_item(rollback, engine_session, engine_topic, {}, None,
989 cherrypy.request.headers)
990 completed = self.engine.upload_content(engine_session, engine_topic, _id, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +0100991 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +0100992 if completed:
tiernob24258a2018-10-04 18:39:49 +0200993 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100994 else:
995 cherrypy.response.headers["Transaction-Id"] = _id
996 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +0200997 elif topic == "ns_instances_content":
998 # creates NSR
tierno701018c2019-06-25 11:13:14 +0000999 _id = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs)
tiernob24258a2018-10-04 18:39:49 +02001000 # creates nslcmop
1001 indata["lcmOperationType"] = "instantiate"
1002 indata["nsInstanceId"] = _id
tierno701018c2019-06-25 11:13:14 +00001003 nslcmop_id = self.engine.new_item(rollback, engine_session, "nslcmops", indata, None)
tiernob24258a2018-10-04 18:39:49 +02001004 self._set_location_header(main_topic, version, topic, _id)
kuuse078f55e2019-05-16 19:24:21 +02001005 outdata = {"id": _id, "nslcmop_id": nslcmop_id}
tiernob24258a2018-10-04 18:39:49 +02001006 elif topic == "ns_instances" and item:
1007 indata["lcmOperationType"] = item
1008 indata["nsInstanceId"] = _id
tierno701018c2019-06-25 11:13:14 +00001009 _id = self.engine.new_item(rollback, engine_session, "nslcmops", indata, kwargs)
tiernob24258a2018-10-04 18:39:49 +02001010 self._set_location_header(main_topic, version, "ns_lcm_op_occs", _id)
tierno65acb4d2018-04-06 16:42:40 +02001011 outdata = {"id": _id}
1012 cherrypy.response.status = HTTPStatus.ACCEPTED.value
garciadeblas9750c5a2018-10-15 16:20:35 +02001013 elif topic == "netslice_instances_content":
Felipe Vicens07f31722018-10-29 15:16:44 +01001014 # creates NetSlice_Instance_record (NSIR)
tierno701018c2019-06-25 11:13:14 +00001015 _id = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs)
Felipe Vicens07f31722018-10-29 15:16:44 +01001016 self._set_location_header(main_topic, version, topic, _id)
garciadeblas9750c5a2018-10-15 16:20:35 +02001017 indata["lcmOperationType"] = "instantiate"
Felipe Vicens126af572019-06-05 19:13:04 +02001018 indata["netsliceInstanceId"] = _id
tierno701018c2019-06-25 11:13:14 +00001019 nsilcmop_id = self.engine.new_item(rollback, engine_session, "nsilcmops", indata, kwargs)
kuuse078f55e2019-05-16 19:24:21 +02001020 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
delacruzramoc061f562019-04-05 11:00:02 +02001021
garciadeblas9750c5a2018-10-15 16:20:35 +02001022 elif topic == "netslice_instances" and item:
1023 indata["lcmOperationType"] = item
Felipe Vicens126af572019-06-05 19:13:04 +02001024 indata["netsliceInstanceId"] = _id
tierno701018c2019-06-25 11:13:14 +00001025 _id = self.engine.new_item(rollback, engine_session, "nsilcmops", indata, kwargs)
garciadeblas9750c5a2018-10-15 16:20:35 +02001026 self._set_location_header(main_topic, version, "nsi_lcm_op_occs", _id)
1027 outdata = {"id": _id}
1028 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +01001029 else:
tierno701018c2019-06-25 11:13:14 +00001030 _id = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +01001031 cherrypy.request.headers)
tiernob24258a2018-10-04 18:39:49 +02001032 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001033 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +02001034 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tiernof27c79b2018-03-12 17:08:42 +01001035 cherrypy.response.status = HTTPStatus.CREATED.value
tierno09c073e2018-04-26 13:36:48 +02001036
tiernoc94c3df2018-02-09 15:38:54 +01001037 elif method == "DELETE":
1038 if not _id:
tierno701018c2019-06-25 11:13:14 +00001039 outdata = self.engine.del_item_list(engine_session, engine_topic, kwargs)
tierno09c073e2018-04-26 13:36:48 +02001040 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +01001041 else: # len(args) > 1
tiernoe8631782018-12-21 13:31:52 +00001042 delete_in_process = False
tierno701018c2019-06-25 11:13:14 +00001043 if topic == "ns_instances_content" and not engine_session["force"]:
tiernob24258a2018-10-04 18:39:49 +02001044 nslcmop_desc = {
1045 "lcmOperationType": "terminate",
1046 "nsInstanceId": _id,
1047 "autoremove": True
1048 }
tierno701018c2019-06-25 11:13:14 +00001049 opp_id = self.engine.new_item(rollback, engine_session, "nslcmops", nslcmop_desc, None)
tiernoe8631782018-12-21 13:31:52 +00001050 if opp_id:
1051 delete_in_process = True
1052 outdata = {"_id": opp_id}
1053 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tierno701018c2019-06-25 11:13:14 +00001054 elif topic == "netslice_instances_content" and not engine_session["force"]:
garciadeblas9750c5a2018-10-15 16:20:35 +02001055 nsilcmop_desc = {
1056 "lcmOperationType": "terminate",
Felipe Vicens126af572019-06-05 19:13:04 +02001057 "netsliceInstanceId": _id,
garciadeblas9750c5a2018-10-15 16:20:35 +02001058 "autoremove": True
1059 }
tierno701018c2019-06-25 11:13:14 +00001060 opp_id = self.engine.new_item(rollback, engine_session, "nsilcmops", nsilcmop_desc, None)
tiernoe8631782018-12-21 13:31:52 +00001061 if opp_id:
1062 delete_in_process = True
1063 outdata = {"_id": opp_id}
1064 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1065 if not delete_in_process:
tierno701018c2019-06-25 11:13:14 +00001066 self.engine.del_item(engine_session, engine_topic, _id)
tierno09c073e2018-04-26 13:36:48 +02001067 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tierno55ba2e62018-12-11 17:22:22 +00001068 if engine_topic in ("vim_accounts", "wim_accounts", "sdns"):
tierno09c073e2018-04-26 13:36:48 +02001069 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1070
tierno7ae10112018-05-18 14:36:02 +02001071 elif method in ("PUT", "PATCH"):
tiernocd54a4a2018-09-12 16:40:35 +02001072 outdata = None
tierno701018c2019-06-25 11:13:14 +00001073 if not indata and not kwargs and not engine_session.get("set_project"):
tiernoc94c3df2018-02-09 15:38:54 +01001074 raise NbiException("Nothing to update. Provide payload and/or query string",
1075 HTTPStatus.BAD_REQUEST)
garciadeblas9750c5a2018-10-15 16:20:35 +02001076 if item in ("nsd_content", "package_content", "nst_content") and method == "PUT":
tierno701018c2019-06-25 11:13:14 +00001077 completed = self.engine.upload_content(engine_session, engine_topic, _id, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +01001078 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +01001079 if not completed:
1080 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +01001081 else:
tierno701018c2019-06-25 11:13:14 +00001082 self.engine.edit_item(engine_session, engine_topic, _id, indata, kwargs)
tiernocd54a4a2018-09-12 16:40:35 +02001083 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernoc94c3df2018-02-09 15:38:54 +01001084 else:
1085 raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tiernoa6bb45d2019-06-14 09:45:39 +00001086
1087 # if Role information changes, it is needed to reload the information of roles
1088 if topic == "roles" and method != "GET":
1089 self.authenticator.load_operation_to_allowed_roles()
tierno701018c2019-06-25 11:13:14 +00001090 return self._format_out(outdata, token_info, _format)
tiernob24258a2018-10-04 18:39:49 +02001091 except Exception as e:
tierno36ec8602018-11-02 17:27:11 +01001092 if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException,
1093 ValidationError)):
tiernob24258a2018-10-04 18:39:49 +02001094 http_code_value = cherrypy.response.status = e.http_code.value
1095 http_code_name = e.http_code.name
1096 cherrypy.log("Exception {}".format(e))
1097 else:
1098 http_code_value = cherrypy.response.status = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Felipe Vicense36ab852018-11-23 14:12:09 +01001099 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
tiernob24258a2018-10-04 18:39:49 +02001100 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +02001101 if hasattr(outdata, "close"): # is an open file
1102 outdata.close()
tiernob24258a2018-10-04 18:39:49 +02001103 error_text = str(e)
1104 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +02001105 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +02001106 try:
tiernocc103432018-10-19 14:10:35 +02001107 if rollback_item.get("operation") == "set":
1108 self.engine.db.set_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
1109 rollback_item["content"], fail_on_empty=False)
1110 else:
tiernoe8631782018-12-21 13:31:52 +00001111 self.engine.db.del_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
1112 fail_on_empty=False)
tierno3ace63c2018-05-03 17:51:43 +02001113 except Exception as e2:
tiernob24258a2018-10-04 18:39:49 +02001114 rollback_error_text = "Rollback Exception {}: {}".format(rollback_item, e2)
1115 cherrypy.log(rollback_error_text)
1116 error_text += ". " + rollback_error_text
1117 # if isinstance(e, MsgException):
1118 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1119 # engine_topic[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +01001120 problem_details = {
tiernob24258a2018-10-04 18:39:49 +02001121 "code": http_code_name,
1122 "status": http_code_value,
1123 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +01001124 }
tierno701018c2019-06-25 11:13:14 +00001125 return self._format_out(problem_details, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001126 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1127
1128
tiernoc94c3df2018-02-09 15:38:54 +01001129def _start_service():
1130 """
1131 Callback function called when cherrypy.engine starts
1132 Override configuration with env variables
1133 Set database, storage, message configuration
1134 Init database with admin/admin user password
1135 """
tierno932499c2019-01-28 17:28:10 +00001136 global nbi_server
1137 global subscription_thread
tiernoc94c3df2018-02-09 15:38:54 +01001138 cherrypy.log.error("Starting osm_nbi")
1139 # update general cherrypy configuration
1140 update_dict = {}
1141
1142 engine_config = cherrypy.tree.apps['/osm'].config
1143 for k, v in environ.items():
1144 if not k.startswith("OSMNBI_"):
1145 continue
tiernoe1281182018-05-22 12:24:36 +02001146 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +01001147 if not k2:
1148 continue
1149 try:
1150 # update static configuration
1151 if k == 'OSMNBI_STATIC_DIR':
1152 engine_config["/static"]['tools.staticdir.dir'] = v
1153 engine_config["/static"]['tools.staticdir.on'] = True
1154 elif k == 'OSMNBI_SOCKET_PORT' or k == 'OSMNBI_SERVER_PORT':
1155 update_dict['server.socket_port'] = int(v)
1156 elif k == 'OSMNBI_SOCKET_HOST' or k == 'OSMNBI_SERVER_HOST':
1157 update_dict['server.socket_host'] = v
tiernof5298be2018-05-16 14:43:57 +02001158 elif k1 in ("server", "test", "auth", "log"):
1159 update_dict[k1 + '.' + k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001160 elif k1 in ("message", "database", "storage", "authentication"):
tiernof5298be2018-05-16 14:43:57 +02001161 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001162 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +01001163 engine_config[k1][k2] = int(v)
1164 else:
1165 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001166
tiernoc94c3df2018-02-09 15:38:54 +01001167 except ValueError as e:
1168 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1169 except Exception as e:
1170 cherrypy.log.warn("skipping environ '{}' on exception '{}'".format(k, e))
1171
1172 if update_dict:
1173 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +02001174 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001175
1176 # logging cherrypy
1177 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1178 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
1179 logger_server = logging.getLogger("cherrypy.error")
1180 logger_access = logging.getLogger("cherrypy.access")
1181 logger_cherry = logging.getLogger("cherrypy")
1182 logger_nbi = logging.getLogger("nbi")
1183
tiernof5298be2018-05-16 14:43:57 +02001184 if "log.file" in engine_config["global"]:
1185 file_handler = logging.handlers.RotatingFileHandler(engine_config["global"]["log.file"],
tiernoc94c3df2018-02-09 15:38:54 +01001186 maxBytes=100e6, backupCount=9, delay=0)
1187 file_handler.setFormatter(log_formatter_simple)
1188 logger_cherry.addHandler(file_handler)
1189 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +02001190 # log always to standard output
1191 for format_, logger in {"nbi.server %(filename)s:%(lineno)s": logger_server,
1192 "nbi.access %(filename)s:%(lineno)s": logger_access,
1193 "%(name)s %(filename)s:%(lineno)s": logger_nbi
1194 }.items():
1195 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
1196 log_formatter_cherry = logging.Formatter(log_format_cherry, datefmt='%Y-%m-%dT%H:%M:%S')
1197 str_handler = logging.StreamHandler()
1198 str_handler.setFormatter(log_formatter_cherry)
1199 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +01001200
tiernof5298be2018-05-16 14:43:57 +02001201 if engine_config["global"].get("log.level"):
1202 logger_cherry.setLevel(engine_config["global"]["log.level"])
1203 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +01001204
1205 # logging other modules
1206 for k1, logname in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
1207 engine_config[k1]["logger_name"] = logname
1208 logger_module = logging.getLogger(logname)
1209 if "logfile" in engine_config[k1]:
1210 file_handler = logging.handlers.RotatingFileHandler(engine_config[k1]["logfile"],
tierno2236d202018-05-16 19:05:16 +02001211 maxBytes=100e6, backupCount=9, delay=0)
tiernoc94c3df2018-02-09 15:38:54 +01001212 file_handler.setFormatter(log_formatter_simple)
1213 logger_module.addHandler(file_handler)
1214 if "loglevel" in engine_config[k1]:
1215 logger_module.setLevel(engine_config[k1]["loglevel"])
1216 # TODO add more entries, e.g.: storage
1217 cherrypy.tree.apps['/osm'].root.engine.start(engine_config)
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001218 cherrypy.tree.apps['/osm'].root.authenticator.start(engine_config)
tiernod985a8d2018-10-19 14:12:28 +02001219 cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version)
1220 cherrypy.tree.apps['/osm'].root.authenticator.init_db(target_version=auth_database_version)
tiernobee508e2019-01-21 11:21:49 +00001221
tierno932499c2019-01-28 17:28:10 +00001222 # start subscriptions thread:
1223 subscription_thread = SubscriptionThread(config=engine_config, engine=nbi_server.engine)
1224 subscription_thread.start()
1225 # Do not capture except SubscriptionException
1226
tiernobee508e2019-01-21 11:21:49 +00001227 # load and print version. Ignore possible errors, e.g. file not found
1228 try:
1229 with open("{}/version".format(engine_config["/static"]['tools.staticdir.dir'])) as version_file:
1230 version_data = version_file.read()
1231 cherrypy.log.error("Starting OSM NBI Version: {}".format(version_data.replace("\n", " ")))
1232 except Exception:
1233 pass
tiernoc94c3df2018-02-09 15:38:54 +01001234
1235
1236def _stop_service():
1237 """
1238 Callback function called when cherrypy.engine stops
1239 TODO: Ending database connections.
1240 """
tierno932499c2019-01-28 17:28:10 +00001241 global subscription_thread
tierno65ca36d2019-02-12 19:27:52 +01001242 if subscription_thread:
1243 subscription_thread.terminate()
tierno932499c2019-01-28 17:28:10 +00001244 subscription_thread = None
tiernoc94c3df2018-02-09 15:38:54 +01001245 cherrypy.tree.apps['/osm'].root.engine.stop()
1246 cherrypy.log.error("Stopping osm_nbi")
1247
tierno2236d202018-05-16 19:05:16 +02001248
tiernof5298be2018-05-16 14:43:57 +02001249def nbi(config_file):
tierno932499c2019-01-28 17:28:10 +00001250 global nbi_server
tiernoc94c3df2018-02-09 15:38:54 +01001251 # conf = {
1252 # '/': {
1253 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1254 # 'tools.sessions.on': True,
1255 # 'tools.response_headers.on': True,
1256 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1257 # }
1258 # }
1259 # cherrypy.Server.ssl_module = 'builtin'
1260 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1261 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1262 # cherrypy.Server.thread_pool = 10
1263 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1264
1265 # cherrypy.config.update({'tools.auth_basic.on': True,
1266 # 'tools.auth_basic.realm': 'localhost',
1267 # 'tools.auth_basic.checkpassword': validate_password})
tierno932499c2019-01-28 17:28:10 +00001268 nbi_server = Server()
tiernoc94c3df2018-02-09 15:38:54 +01001269 cherrypy.engine.subscribe('start', _start_service)
1270 cherrypy.engine.subscribe('stop', _stop_service)
tierno932499c2019-01-28 17:28:10 +00001271 cherrypy.quickstart(nbi_server, '/osm', config_file)
tiernof5298be2018-05-16 14:43:57 +02001272
1273
1274def usage():
1275 print("""Usage: {} [options]
1276 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1277 -h|--help: shows this help
1278 """.format(sys.argv[0]))
tierno2236d202018-05-16 19:05:16 +02001279 # --log-socket-host HOST: send logs to this host")
1280 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +01001281
1282
1283if __name__ == '__main__':
tiernof5298be2018-05-16 14:43:57 +02001284 try:
1285 # load parameters and configuration
1286 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
1287 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1288 config_file = None
1289 for o, a in opts:
1290 if o in ("-h", "--help"):
1291 usage()
1292 sys.exit()
1293 elif o in ("-c", "--config"):
1294 config_file = a
1295 # elif o == "--log-socket-port":
1296 # log_socket_port = a
1297 # elif o == "--log-socket-host":
1298 # log_socket_host = a
1299 # elif o == "--log-file":
1300 # log_file = a
1301 else:
1302 assert False, "Unhandled option"
1303 if config_file:
1304 if not path.isfile(config_file):
1305 print("configuration file '{}' that not exist".format(config_file), file=sys.stderr)
1306 exit(1)
1307 else:
1308 for config_file in (__file__[:__file__.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
1309 if path.isfile(config_file):
1310 break
1311 else:
1312 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys.stderr)
1313 exit(1)
1314 nbi(config_file)
1315 except getopt.GetoptError as e:
1316 print(str(e), file=sys.stderr)
1317 # usage()
1318 exit(1)