blob: 0616430bd449503d29d54322f39d15ef6458660c [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"
Eduardo Sousa044f4312019-05-20 15:17:35 +010043database_version = '1.1'
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
193
194class NbiException(Exception):
195
196 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
197 Exception.__init__(self, message)
198 self.http_code = http_code
199
200
201class Server(object):
202 instance = 0
203 # to decode bytes to str
204 reader = getreader("utf-8")
205
206 def __init__(self):
207 self.instance += 1
208 self.engine = Engine()
Eduardo Sousad1b525d2018-10-04 04:24:18 +0100209 self.authenticator = Authenticator()
tiernof27c79b2018-03-12 17:08:42 +0100210 self.valid_methods = { # contains allowed URL and methods
211 "admin": {
212 "v1": {
tierno0f98af52018-03-19 10:28:22 +0100213 "tokens": {"METHODS": ("GET", "POST", "DELETE"),
tierno2236d202018-05-16 19:05:16 +0200214 "<ID>": {"METHODS": ("GET", "DELETE")}
215 },
tierno0f98af52018-03-19 10:28:22 +0100216 "users": {"METHODS": ("GET", "POST"),
tiernocd54a4a2018-09-12 16:40:35 +0200217 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200218 },
tierno0f98af52018-03-19 10:28:22 +0100219 "projects": {"METHODS": ("GET", "POST"),
delacruzramoc061f562019-04-05 11:00:02 +0200220 # Added PUT to allow Project Name modification
221 "<ID>": {"METHODS": ("GET", "DELETE", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200222 },
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100223 "roles": {"METHODS": ("GET", "POST"),
224 "<ID>": {"METHODS": ("GET", "POST", "DELETE")}
225 },
tierno0f98af52018-03-19 10:28:22 +0100226 "vims": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200227 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200228 },
tierno09c073e2018-04-26 13:36:48 +0200229 "vim_accounts": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200230 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200231 },
tierno55ba2e62018-12-11 17:22:22 +0000232 "wim_accounts": {"METHODS": ("GET", "POST"),
233 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
234 },
tierno0f98af52018-03-19 10:28:22 +0100235 "sdns": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200236 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200237 },
tiernof27c79b2018-03-12 17:08:42 +0100238 }
239 },
tiernocb83c942018-09-24 17:28:13 +0200240 "pdu": {
241 "v1": {
242 "pdu_descriptors": {"METHODS": ("GET", "POST"),
243 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT")}
244 },
245 }
246 },
tiernof27c79b2018-03-12 17:08:42 +0100247 "nsd": {
248 "v1": {
tierno2236d202018-05-16 19:05:16 +0200249 "ns_descriptors_content": {"METHODS": ("GET", "POST"),
250 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
251 },
252 "ns_descriptors": {"METHODS": ("GET", "POST"),
tierno36ec8602018-11-02 17:27:11 +0100253 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno2236d202018-05-16 19:05:16 +0200254 "nsd_content": {"METHODS": ("GET", "PUT")},
255 "nsd": {"METHODS": "GET"}, # descriptor inside package
256 "artifacts": {"*": {"METHODS": "GET"}}
257 }
258 },
tiernof27c79b2018-03-12 17:08:42 +0100259 "pnf_descriptors": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200260 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
261 "pnfd_content": {"TODO": ("GET", "PUT")}
262 }
263 },
tiernof27c79b2018-03-12 17:08:42 +0100264 "subscriptions": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200265 "<ID>": {"TODO": ("GET", "DELETE")}
266 },
tiernof27c79b2018-03-12 17:08:42 +0100267 }
268 },
269 "vnfpkgm": {
270 "v1": {
tierno2236d202018-05-16 19:05:16 +0200271 "vnf_packages_content": {"METHODS": ("GET", "POST"),
272 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
273 },
274 "vnf_packages": {"METHODS": ("GET", "POST"),
tiernoaae4dc42018-06-11 18:55:50 +0200275 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
tierno2236d202018-05-16 19:05:16 +0200276 "package_content": {"METHODS": ("GET", "PUT"), # package
277 "upload_from_uri": {"TODO": "POST"}
278 },
279 "vnfd": {"METHODS": "GET"}, # descriptor inside package
280 "artifacts": {"*": {"METHODS": "GET"}}
281 }
282 },
tiernof27c79b2018-03-12 17:08:42 +0100283 "subscriptions": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200284 "<ID>": {"TODO": ("GET", "DELETE")}
285 },
tiernof27c79b2018-03-12 17:08:42 +0100286 }
287 },
288 "nslcm": {
289 "v1": {
290 "ns_instances_content": {"METHODS": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200291 "<ID>": {"METHODS": ("GET", "DELETE")}
292 },
tierno65acb4d2018-04-06 16:42:40 +0200293 "ns_instances": {"METHODS": ("GET", "POST"),
tierno95692442018-05-24 18:05:28 +0200294 "<ID>": {"METHODS": ("GET", "DELETE"),
tiernof759d822018-06-11 18:54:54 +0200295 "scale": {"METHODS": "POST"},
tierno2236d202018-05-16 19:05:16 +0200296 "terminate": {"METHODS": "POST"},
297 "instantiate": {"METHODS": "POST"},
298 "action": {"METHODS": "POST"},
299 }
300 },
tierno65acb4d2018-04-06 16:42:40 +0200301 "ns_lcm_op_occs": {"METHODS": "GET",
tierno2236d202018-05-16 19:05:16 +0200302 "<ID>": {"METHODS": "GET"},
303 },
tierno0ffaa992018-05-09 13:21:56 +0200304 "vnfrs": {"METHODS": ("GET"),
tierno2236d202018-05-16 19:05:16 +0200305 "<ID>": {"METHODS": ("GET")}
306 },
tiernof759d822018-06-11 18:54:54 +0200307 "vnf_instances": {"METHODS": ("GET"),
308 "<ID>": {"METHODS": ("GET")}
309 },
tiernof27c79b2018-03-12 17:08:42 +0100310 }
311 },
garciadeblas9750c5a2018-10-15 16:20:35 +0200312 "nst": {
313 "v1": {
314 "netslice_templates_content": {"METHODS": ("GET", "POST"),
315 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
316 },
317 "netslice_templates": {"METHODS": ("GET", "POST"),
318 "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH",
319 "nst_content": {"METHODS": ("GET", "PUT")},
320 "nst": {"METHODS": "GET"}, # descriptor inside package
321 "artifacts": {"*": {"METHODS": "GET"}}
322 }
323 },
324 "subscriptions": {"TODO": ("GET", "POST"),
325 "<ID>": {"TODO": ("GET", "DELETE")}
326 },
327 }
328 },
329 "nsilcm": {
330 "v1": {
331 "netslice_instances_content": {"METHODS": ("GET", "POST"),
332 "<ID>": {"METHODS": ("GET", "DELETE")}
333 },
334 "netslice_instances": {"METHODS": ("GET", "POST"),
335 "<ID>": {"METHODS": ("GET", "DELETE"),
336 "terminate": {"METHODS": "POST"},
337 "instantiate": {"METHODS": "POST"},
338 "action": {"METHODS": "POST"},
339 }
340 },
341 "nsi_lcm_op_occs": {"METHODS": "GET",
342 "<ID>": {"METHODS": "GET"},
343 },
344 }
345 },
vijay.r35ef2f72019-04-30 17:55:49 +0530346 "nspm": {
347 "v1": {
348 "pm_jobs": {
349 "<ID>": {
350 "reports": {
351 "<ID>": {"METHODS": ("GET")}
352 }
353 },
354 },
355 },
356 },
tiernof27c79b2018-03-12 17:08:42 +0100357 }
tiernoc94c3df2018-02-09 15:38:54 +0100358
tiernoc94c3df2018-02-09 15:38:54 +0100359 def _format_in(self, kwargs):
360 try:
361 indata = None
362 if cherrypy.request.body.length:
363 error_text = "Invalid input format "
364
365 if "Content-Type" in cherrypy.request.headers:
366 if "application/json" in cherrypy.request.headers["Content-Type"]:
367 error_text = "Invalid json format "
368 indata = json.load(self.reader(cherrypy.request.body))
gcalvinode4adfe2018-10-30 11:46:09 +0100369 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100370 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
371 error_text = "Invalid yaml format "
372 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100373 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100374 elif "application/binary" in cherrypy.request.headers["Content-Type"] or \
375 "application/gzip" in cherrypy.request.headers["Content-Type"] or \
tiernof27c79b2018-03-12 17:08:42 +0100376 "application/zip" in cherrypy.request.headers["Content-Type"] or \
377 "text/plain" in cherrypy.request.headers["Content-Type"]:
378 indata = cherrypy.request.body # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100379 elif "multipart/form-data" in cherrypy.request.headers["Content-Type"]:
380 if "descriptor_file" in kwargs:
381 filecontent = kwargs.pop("descriptor_file")
382 if not filecontent.file:
383 raise NbiException("empty file or content", HTTPStatus.BAD_REQUEST)
tiernof27c79b2018-03-12 17:08:42 +0100384 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100385 if filecontent.content_type.value:
386 cherrypy.request.headers["Content-Type"] = filecontent.content_type.value
387 else:
388 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
389 # "Only 'Content-Type' of type 'application/json' or
390 # 'application/yaml' for input format are available")
391 error_text = "Invalid yaml format "
392 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100393 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100394 else:
395 error_text = "Invalid yaml format "
396 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100397 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100398 if not indata:
399 indata = {}
400
tiernoc94c3df2018-02-09 15:38:54 +0100401 format_yaml = False
402 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
403 format_yaml = True
404
405 for k, v in kwargs.items():
406 if isinstance(v, str):
407 if v == "":
408 kwargs[k] = None
409 elif format_yaml:
410 try:
411 kwargs[k] = yaml.load(v)
tiernoe1281182018-05-22 12:24:36 +0200412 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100413 pass
414 elif k.endswith(".gt") or k.endswith(".lt") or k.endswith(".gte") or k.endswith(".lte"):
415 try:
416 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200417 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100418 try:
419 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200420 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100421 pass
422 elif v.find(",") > 0:
423 kwargs[k] = v.split(",")
424 elif isinstance(v, (list, tuple)):
425 for index in range(0, len(v)):
426 if v[index] == "":
427 v[index] = None
428 elif format_yaml:
429 try:
430 v[index] = yaml.load(v[index])
tiernoe1281182018-05-22 12:24:36 +0200431 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100432 pass
433
tiernof27c79b2018-03-12 17:08:42 +0100434 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100435 except (ValueError, yaml.YAMLError) as exc:
436 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
437 except KeyError as exc:
438 raise NbiException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
tiernob92094f2018-05-11 13:44:22 +0200439 except Exception as exc:
440 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100441
442 @staticmethod
tiernof27c79b2018-03-12 17:08:42 +0100443 def _format_out(data, session=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100444 """
445 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100446 :param data: response to be sent. Can be a dict, text or file
tiernoc94c3df2018-02-09 15:38:54 +0100447 :param session:
tiernof27c79b2018-03-12 17:08:42 +0100448 :param _format: The format to be set as Content-Type ir data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100449 :return: None
450 """
tierno0f98af52018-03-19 10:28:22 +0100451 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100452 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100453 if accept and "text/html" in accept:
454 return html.format(data, cherrypy.request, cherrypy.response, session)
tierno09c073e2018-04-26 13:36:48 +0200455 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100456 return
457 elif hasattr(data, "read"): # file object
458 if _format:
459 cherrypy.response.headers["Content-Type"] = _format
460 elif "b" in data.mode: # binariy asssumig zip
461 cherrypy.response.headers["Content-Type"] = 'application/zip'
462 else:
463 cherrypy.response.headers["Content-Type"] = 'text/plain'
464 # TODO check that cherrypy close file. If not implement pending things to close per thread next
465 return data
tierno0f98af52018-03-19 10:28:22 +0100466 if accept:
tiernoc94c3df2018-02-09 15:38:54 +0100467 if "application/json" in accept:
468 cherrypy.response.headers["Content-Type"] = 'application/json; charset=utf-8'
469 a = json.dumps(data, indent=4) + "\n"
470 return a.encode("utf8")
471 elif "text/html" in accept:
472 return html.format(data, cherrypy.request, cherrypy.response, session)
473
tiernof27c79b2018-03-12 17:08:42 +0100474 elif "application/yaml" in accept or "*/*" in accept or "text/plain" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100475 pass
tiernof717cbe2018-12-03 16:35:42 +0000476 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
477 elif cherrypy.response.status >= 400:
tiernoc94c3df2018-02-09 15:38:54 +0100478 raise cherrypy.HTTPError(HTTPStatus.NOT_ACCEPTABLE.value,
479 "Only 'Accept' of type 'application/json' or 'application/yaml' "
480 "for output format are available")
481 cherrypy.response.headers["Content-Type"] = 'application/yaml'
482 return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False,
483 encoding='utf-8', allow_unicode=True) # , canonical=True, default_style='"'
484
485 @cherrypy.expose
486 def index(self, *args, **kwargs):
487 session = None
488 try:
489 if cherrypy.request.method == "GET":
Eduardo Sousa2f988212018-07-26 01:04:11 +0100490 session = self.authenticator.authorize()
tiernoc94c3df2018-02-09 15:38:54 +0100491 outdata = "Index page"
492 else:
493 raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value,
tierno2236d202018-05-16 19:05:16 +0200494 "Method {} not allowed for tokens".format(cherrypy.request.method))
tiernoc94c3df2018-02-09 15:38:54 +0100495
496 return self._format_out(outdata, session)
497
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100498 except (EngineException, AuthException) as e:
tiernoc94c3df2018-02-09 15:38:54 +0100499 cherrypy.log("index Exception {}".format(e))
500 cherrypy.response.status = e.http_code.value
501 return self._format_out("Welcome to OSM!", session)
502
503 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200504 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200505 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200506 global __version__, version_date
507 try:
508 if cherrypy.request.method != "GET":
509 raise NbiException("Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED)
510 elif args or kwargs:
511 raise NbiException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED)
512 return __version__ + " " + version_date
513 except NbiException as e:
514 cherrypy.response.status = e.http_code.value
515 problem_details = {
516 "code": e.http_code.name,
517 "status": e.http_code.value,
518 "detail": str(e),
519 }
520 return self._format_out(problem_details, None)
521
522 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100523 def token(self, method, token_id=None, kwargs=None):
tiernoc94c3df2018-02-09 15:38:54 +0100524 session = None
525 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +0100526 indata = self._format_in(kwargs)
527 if not isinstance(indata, dict):
528 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100529 try:
tiernoc94c3df2018-02-09 15:38:54 +0100530 if method == "GET":
Eduardo Sousa2f988212018-07-26 01:04:11 +0100531 session = self.authenticator.authorize()
tiernof27c79b2018-03-12 17:08:42 +0100532 if token_id:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100533 outdata = self.authenticator.get_token(session, token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100534 else:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100535 outdata = self.authenticator.get_token_list(session)
tiernoc94c3df2018-02-09 15:38:54 +0100536 elif method == "POST":
537 try:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100538 session = self.authenticator.authorize()
tiernoe1281182018-05-22 12:24:36 +0200539 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100540 session = None
541 if kwargs:
542 indata.update(kwargs)
Eduardo Sousa2f988212018-07-26 01:04:11 +0100543 outdata = self.authenticator.new_token(session, indata, cherrypy.request.remote)
tiernoc94c3df2018-02-09 15:38:54 +0100544 session = outdata
545 cherrypy.session['Authorization'] = outdata["_id"]
tierno0f98af52018-03-19 10:28:22 +0100546 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
tiernoc94c3df2018-02-09 15:38:54 +0100547 # cherrypy.response.cookie["Authorization"] = outdata["id"]
548 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
549 elif method == "DELETE":
tiernof27c79b2018-03-12 17:08:42 +0100550 if not token_id and "id" in kwargs:
tiernoc94c3df2018-02-09 15:38:54 +0100551 token_id = kwargs["id"]
tiernof27c79b2018-03-12 17:08:42 +0100552 elif not token_id:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100553 session = self.authenticator.authorize()
tiernoc94c3df2018-02-09 15:38:54 +0100554 token_id = session["_id"]
Eduardo Sousa2f988212018-07-26 01:04:11 +0100555 outdata = self.authenticator.del_token(token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100556 session = None
557 cherrypy.session['Authorization'] = "logout"
558 # cherrypy.response.cookie["Authorization"] = token_id
559 # cherrypy.response.cookie["Authorization"]['expires'] = 0
560 else:
561 raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
562 return self._format_out(outdata, session)
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100563 except (NbiException, EngineException, DbException, AuthException) as e:
tiernoc94c3df2018-02-09 15:38:54 +0100564 cherrypy.log("tokens Exception {}".format(e))
565 cherrypy.response.status = e.http_code.value
566 problem_details = {
567 "code": e.http_code.name,
568 "status": e.http_code.value,
569 "detail": str(e),
570 }
571 return self._format_out(problem_details, session)
572
573 @cherrypy.expose
574 def test(self, *args, **kwargs):
575 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +0100576 if args and args[0] == "help":
tiernof717cbe2018-12-03 16:35:42 +0000577 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
tiernoe1281182018-05-22 12:24:36 +0200578 "sleep/<time>\nmessage/topic\n</pre></html>"
tiernof27c79b2018-03-12 17:08:42 +0100579
580 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +0100581 try:
582 # self.engine.load_dbase(cherrypy.request.app.config)
583 self.engine.create_admin()
584 return "Done. User 'admin', password 'admin' created"
585 except Exception:
586 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
587 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +0100588 elif args and args[0] == "file":
589 return cherrypy.lib.static.serve_file(cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1],
590 "text/plain", "attachment")
591 elif args and args[0] == "file2":
592 f_path = cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1]
593 f = open(f_path, "r")
594 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100595 return f
tierno55945e72018-04-06 16:40:27 +0200596
tiernof27c79b2018-03-12 17:08:42 +0100597 elif len(args) == 2 and args[0] == "db-clear":
tiernof717cbe2018-12-03 16:35:42 +0000598 deleted_info = self.engine.db.del_list(args[1], kwargs)
599 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
600 elif len(args) and args[0] == "fs-clear":
601 if len(args) >= 2:
602 folders = (args[1],)
603 else:
604 folders = self.engine.fs.dir_ls(".")
605 for folder in folders:
606 self.engine.fs.file_delete(folder)
607 return ",".join(folders) + " folders deleted\n"
tiernoc94c3df2018-02-09 15:38:54 +0100608 elif args and args[0] == "login":
609 if not cherrypy.request.headers.get("Authorization"):
610 cherrypy.response.headers["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
611 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
612 elif args and args[0] == "login2":
613 if not cherrypy.request.headers.get("Authorization"):
614 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
615 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
616 elif args and args[0] == "sleep":
617 sleep_time = 5
618 try:
619 sleep_time = int(args[1])
620 except Exception:
621 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
622 return self._format_out("Database already initialized")
623 thread_info = cherrypy.thread_data
624 print(thread_info)
625 time.sleep(sleep_time)
626 # thread_info
627 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +0200628 main_topic = args[1]
629 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +0100630 try:
tierno55945e72018-04-06 16:40:27 +0200631 if cherrypy.request.method == 'POST':
632 to_send = yaml.load(cherrypy.request.body)
633 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +0200634 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +0200635 return_text += " {}: {}\n".format(k, v)
636 elif cherrypy.request.method == 'GET':
637 for k, v in kwargs.items():
tiernob24258a2018-10-04 18:39:49 +0200638 self.engine.msg.write(main_topic, k, yaml.load(v))
tierno55945e72018-04-06 16:40:27 +0200639 return_text += " {}: {}\n".format(k, yaml.load(v))
tiernoc94c3df2018-02-09 15:38:54 +0100640 except Exception as e:
tierno55945e72018-04-06 16:40:27 +0200641 return_text += "Error: " + str(e)
642 return_text += "</pre></html>\n"
643 return return_text
tiernoc94c3df2018-02-09 15:38:54 +0100644
645 return_text = (
646 "<html><pre>\nheaders:\n args: {}\n".format(args) +
647 " kwargs: {}\n".format(kwargs) +
648 " headers: {}\n".format(cherrypy.request.headers) +
649 " path_info: {}\n".format(cherrypy.request.path_info) +
650 " query_string: {}\n".format(cherrypy.request.query_string) +
651 " session: {}\n".format(cherrypy.session) +
652 " cookie: {}\n".format(cherrypy.request.cookie) +
653 " method: {}\n".format(cherrypy.request.method) +
tiernof717cbe2018-12-03 16:35:42 +0000654 " session: {}\n".format(cherrypy.session.get('fieldname')) +
tiernoc94c3df2018-02-09 15:38:54 +0100655 " body:\n")
656 return_text += " length: {}\n".format(cherrypy.request.body.length)
657 if cherrypy.request.body.length:
658 return_text += " content: {}\n".format(
659 str(cherrypy.request.body.read(int(cherrypy.request.headers.get('Content-Length', 0)))))
660 if thread_info:
661 return_text += "thread: {}\n".format(thread_info)
662 return_text += "</pre></html>"
663 return return_text
664
tiernof27c79b2018-03-12 17:08:42 +0100665 def _check_valid_url_method(self, method, *args):
666 if len(args) < 3:
tiernob24258a2018-10-04 18:39:49 +0200667 raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus.METHOD_NOT_ALLOWED)
tiernof27c79b2018-03-12 17:08:42 +0100668
669 reference = self.valid_methods
670 for arg in args:
671 if arg is None:
672 break
673 if not isinstance(reference, dict):
674 raise NbiException("URL contains unexpected extra items '{}'".format(arg),
675 HTTPStatus.METHOD_NOT_ALLOWED)
676
677 if arg in reference:
678 reference = reference[arg]
679 elif "<ID>" in reference:
680 reference = reference["<ID>"]
681 elif "*" in reference:
682 reference = reference["*"]
683 break
684 else:
685 raise NbiException("Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED)
686 if "TODO" in reference and method in reference["TODO"]:
687 raise NbiException("Method {} not supported yet for this URL".format(method), HTTPStatus.NOT_IMPLEMENTED)
tierno2236d202018-05-16 19:05:16 +0200688 elif "METHODS" in reference and method not in reference["METHODS"]:
tiernof27c79b2018-03-12 17:08:42 +0100689 raise NbiException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
690 return
691
692 @staticmethod
tiernob24258a2018-10-04 18:39:49 +0200693 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +0100694 """
695 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +0200696 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +0100697 :param version:
tiernob24258a2018-10-04 18:39:49 +0200698 :param topic:
tiernof27c79b2018-03-12 17:08:42 +0100699 :param id:
700 :return: None
701 """
702 # 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 +0200703 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(main_topic, version, topic, id)
tiernof27c79b2018-03-12 17:08:42 +0100704 return
705
tierno65ca36d2019-02-12 19:27:52 +0100706 @staticmethod
707 def _manage_admin_query(session, kwargs, method, _id):
708 """
709 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
710 Check that users has rights to use them and returs the admin_query
711 :param session: session rights obtained by token
712 :param kwargs: query string input.
713 :param method: http method: GET, POSST, PUT, ...
714 :param _id:
715 :return: admin_query dictionary with keys:
716 public: True, False or None
717 force: True or False
718 project_id: tuple with projects used for accessing an element
719 set_project: tuple with projects that a created element will belong to
720 method: show, list, delete, write
721 """
722 admin_query = {"force": False, "project_id": (session["project_id"], ), "username": session["username"],
723 "admin": session["admin"], "public": None}
724 if kwargs:
725 # FORCE
726 if "FORCE" in kwargs:
727 if kwargs["FORCE"].lower() != "false": # if None or True set force to True
728 admin_query["force"] = True
729 del kwargs["FORCE"]
730 # PUBLIC
731 if "PUBLIC" in kwargs:
732 if kwargs["PUBLIC"].lower() != "false": # if None or True set public to True
733 admin_query["public"] = True
734 else:
735 admin_query["public"] = False
736 del kwargs["PUBLIC"]
737 # ADMIN
738 if "ADMIN" in kwargs:
739 behave_as = kwargs.pop("ADMIN")
740 if behave_as.lower() != "false":
741 if not session["admin"]:
742 raise NbiException("Only admin projects can use 'ADMIN' query string", HTTPStatus.UNAUTHORIZED)
743 if not behave_as or behave_as.lower() == "true": # convert True, None to empty list
744 admin_query["project_id"] = ()
745 elif isinstance(behave_as, (list, tuple)):
746 admin_query["project_id"] = behave_as
747 else: # isinstance(behave_as, str)
748 admin_query["project_id"] = (behave_as, )
749 if "SET_PROJECT" in kwargs:
750 set_project = kwargs.pop("SET_PROJECT")
751 if not set_project:
752 admin_query["set_project"] = list(admin_query["project_id"])
753 else:
754 if isinstance(set_project, str):
755 set_project = (set_project, )
756 if admin_query["project_id"]:
757 for p in set_project:
758 if p not in admin_query["project_id"]:
759 raise NbiException("Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
760 "'ADMIN='{p}'".format(p=p), HTTPStatus.UNAUTHORIZED)
761 admin_query["set_project"] = set_project
762
763 # PROJECT_READ
764 # if "PROJECT_READ" in kwargs:
765 # admin_query["project"] = kwargs.pop("project")
766 # if admin_query["project"] == session["project_id"]:
767 if method == "GET":
768 if _id:
769 admin_query["method"] = "show"
770 else:
771 admin_query["method"] = "list"
772 elif method == "DELETE":
773 admin_query["method"] = "delete"
774 else:
775 admin_query["method"] = "write"
776 return admin_query
777
tiernoc94c3df2018-02-09 15:38:54 +0100778 @cherrypy.expose
tiernob24258a2018-10-04 18:39:49 +0200779 def default(self, main_topic=None, version=None, topic=None, _id=None, item=None, *args, **kwargs):
tiernoc94c3df2018-02-09 15:38:54 +0100780 session = None
tiernof27c79b2018-03-12 17:08:42 +0100781 outdata = None
782 _format = None
tierno0f98af52018-03-19 10:28:22 +0100783 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +0200784 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +0200785 rollback = []
786 session = None
tiernoc94c3df2018-02-09 15:38:54 +0100787 try:
tiernob24258a2018-10-04 18:39:49 +0200788 if not main_topic or not version or not topic:
789 raise NbiException("URL must contain at least 'main_topic/version/topic'",
790 HTTPStatus.METHOD_NOT_ALLOWED)
vijay.r35ef2f72019-04-30 17:55:49 +0530791 if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm", "nspm"):
tiernob24258a2018-10-04 18:39:49 +0200792 raise NbiException("URL main_topic '{}' not supported".format(main_topic),
793 HTTPStatus.METHOD_NOT_ALLOWED)
tiernoc94c3df2018-02-09 15:38:54 +0100794 if version != 'v1':
795 raise NbiException("URL version '{}' not supported".format(version), HTTPStatus.METHOD_NOT_ALLOWED)
796
tiernof27c79b2018-03-12 17:08:42 +0100797 if kwargs and "METHOD" in kwargs and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
798 method = kwargs.pop("METHOD")
799 else:
800 method = cherrypy.request.method
tierno65ca36d2019-02-12 19:27:52 +0100801
tiernob24258a2018-10-04 18:39:49 +0200802 self._check_valid_url_method(method, main_topic, version, topic, _id, item, *args)
tierno65ca36d2019-02-12 19:27:52 +0100803
tiernob24258a2018-10-04 18:39:49 +0200804 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +0100805 return self.token(method, _id, kwargs)
806
tiernoc94c3df2018-02-09 15:38:54 +0100807 # self.engine.load_dbase(cherrypy.request.app.config)
Eduardo Sousa2f988212018-07-26 01:04:11 +0100808 session = self.authenticator.authorize()
tierno65ca36d2019-02-12 19:27:52 +0100809 session = self._manage_admin_query(session, kwargs, method, _id)
tiernof27c79b2018-03-12 17:08:42 +0100810 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +0200811 engine_topic = topic
812 if topic == "subscriptions":
813 engine_topic = main_topic + "_" + topic
vijay.r35ef2f72019-04-30 17:55:49 +0530814 if item and topic != "pm_jobs":
tiernob24258a2018-10-04 18:39:49 +0200815 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +0100816
tiernob24258a2018-10-04 18:39:49 +0200817 if main_topic == "nsd":
818 engine_topic = "nsds"
819 elif main_topic == "vnfpkgm":
820 engine_topic = "vnfds"
821 elif main_topic == "nslcm":
822 engine_topic = "nsrs"
823 if topic == "ns_lcm_op_occs":
824 engine_topic = "nslcmops"
825 if topic == "vnfrs" or topic == "vnf_instances":
826 engine_topic = "vnfrs"
garciadeblas9750c5a2018-10-15 16:20:35 +0200827 elif main_topic == "nst":
828 engine_topic = "nsts"
829 elif main_topic == "nsilcm":
830 engine_topic = "nsis"
831 if topic == "nsi_lcm_op_occs":
delacruzramoc061f562019-04-05 11:00:02 +0200832 engine_topic = "nsilcmops"
tiernob24258a2018-10-04 18:39:49 +0200833 elif main_topic == "pdu":
834 engine_topic = "pdus"
tierno65ca36d2019-02-12 19:27:52 +0100835 if engine_topic == "vims": # TODO this is for backward compatibility, it will be removed in the future
tiernob24258a2018-10-04 18:39:49 +0200836 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +0100837
838 if method == "GET":
Felipe Vicens07f31722018-10-29 15:16:44 +0100839 if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
garciadeblas9750c5a2018-10-15 16:20:35 +0200840 if item in ("vnfd", "nsd", "nst"):
tiernof27c79b2018-03-12 17:08:42 +0100841 path = "$DESCRIPTOR"
842 elif args:
843 path = args
tiernob24258a2018-10-04 18:39:49 +0200844 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +0100845 path = ()
846 else:
847 path = None
tiernob24258a2018-10-04 18:39:49 +0200848 file, _format = self.engine.get_file(session, engine_topic, _id, path,
tierno2236d202018-05-16 19:05:16 +0200849 cherrypy.request.headers.get("Accept"))
tiernof27c79b2018-03-12 17:08:42 +0100850 outdata = file
851 elif not _id:
tiernob24258a2018-10-04 18:39:49 +0200852 outdata = self.engine.get_item_list(session, engine_topic, kwargs)
tiernoc94c3df2018-02-09 15:38:54 +0100853 else:
vijay.r35ef2f72019-04-30 17:55:49 +0530854 if item == "reports":
855 # TODO check that project_id (_id in this context) has permissions
856 _id = args[0]
tiernob24258a2018-10-04 18:39:49 +0200857 outdata = self.engine.get_item(session, engine_topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100858 elif method == "POST":
garciadeblas9750c5a2018-10-15 16:20:35 +0200859 if topic in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"):
tiernof27c79b2018-03-12 17:08:42 +0100860 _id = cherrypy.request.headers.get("Transaction-Id")
861 if not _id:
tierno65ca36d2019-02-12 19:27:52 +0100862 _id = self.engine.new_item(rollback, session, engine_topic, {}, None, cherrypy.request.headers)
tiernob24258a2018-10-04 18:39:49 +0200863 completed = self.engine.upload_content(session, engine_topic, _id, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +0100864 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +0100865 if completed:
tiernob24258a2018-10-04 18:39:49 +0200866 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100867 else:
868 cherrypy.response.headers["Transaction-Id"] = _id
869 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +0200870 elif topic == "ns_instances_content":
871 # creates NSR
tierno65ca36d2019-02-12 19:27:52 +0100872 _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs)
tiernob24258a2018-10-04 18:39:49 +0200873 # creates nslcmop
874 indata["lcmOperationType"] = "instantiate"
875 indata["nsInstanceId"] = _id
876 self.engine.new_item(rollback, session, "nslcmops", indata, None)
877 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100878 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +0200879 elif topic == "ns_instances" and item:
880 indata["lcmOperationType"] = item
881 indata["nsInstanceId"] = _id
882 _id = self.engine.new_item(rollback, session, "nslcmops", indata, kwargs)
883 self._set_location_header(main_topic, version, "ns_lcm_op_occs", _id)
tierno65acb4d2018-04-06 16:42:40 +0200884 outdata = {"id": _id}
885 cherrypy.response.status = HTTPStatus.ACCEPTED.value
garciadeblas9750c5a2018-10-15 16:20:35 +0200886 elif topic == "netslice_instances_content":
Felipe Vicens07f31722018-10-29 15:16:44 +0100887 # creates NetSlice_Instance_record (NSIR)
tierno65ca36d2019-02-12 19:27:52 +0100888 _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs)
Felipe Vicens07f31722018-10-29 15:16:44 +0100889 self._set_location_header(main_topic, version, topic, _id)
garciadeblas9750c5a2018-10-15 16:20:35 +0200890 indata["lcmOperationType"] = "instantiate"
891 indata["nsiInstanceId"] = _id
Felipe Vicens07f31722018-10-29 15:16:44 +0100892 self.engine.new_item(rollback, session, "nsilcmops", indata, kwargs)
garciadeblas9750c5a2018-10-15 16:20:35 +0200893 outdata = {"id": _id}
delacruzramoc061f562019-04-05 11:00:02 +0200894
garciadeblas9750c5a2018-10-15 16:20:35 +0200895 elif topic == "netslice_instances" and item:
896 indata["lcmOperationType"] = item
897 indata["nsiInstanceId"] = _id
898 _id = self.engine.new_item(rollback, session, "nsilcmops", indata, kwargs)
899 self._set_location_header(main_topic, version, "nsi_lcm_op_occs", _id)
900 outdata = {"id": _id}
901 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +0100902 else:
tiernob24258a2018-10-04 18:39:49 +0200903 _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +0100904 cherrypy.request.headers)
tiernob24258a2018-10-04 18:39:49 +0200905 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100906 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +0200907 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tiernof27c79b2018-03-12 17:08:42 +0100908 cherrypy.response.status = HTTPStatus.CREATED.value
tierno09c073e2018-04-26 13:36:48 +0200909
tiernoc94c3df2018-02-09 15:38:54 +0100910 elif method == "DELETE":
911 if not _id:
tiernob24258a2018-10-04 18:39:49 +0200912 outdata = self.engine.del_item_list(session, engine_topic, kwargs)
tierno09c073e2018-04-26 13:36:48 +0200913 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +0100914 else: # len(args) > 1
tiernoe8631782018-12-21 13:31:52 +0000915 delete_in_process = False
tierno65ca36d2019-02-12 19:27:52 +0100916 if topic == "ns_instances_content" and not session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200917 nslcmop_desc = {
918 "lcmOperationType": "terminate",
919 "nsInstanceId": _id,
920 "autoremove": True
921 }
922 opp_id = self.engine.new_item(rollback, session, "nslcmops", nslcmop_desc, None)
tiernoe8631782018-12-21 13:31:52 +0000923 if opp_id:
924 delete_in_process = True
925 outdata = {"_id": opp_id}
926 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tierno65ca36d2019-02-12 19:27:52 +0100927 elif topic == "netslice_instances_content" and not session["force"]:
garciadeblas9750c5a2018-10-15 16:20:35 +0200928 nsilcmop_desc = {
929 "lcmOperationType": "terminate",
930 "nsiInstanceId": _id,
931 "autoremove": True
932 }
933 opp_id = self.engine.new_item(rollback, session, "nsilcmops", nsilcmop_desc, None)
tiernoe8631782018-12-21 13:31:52 +0000934 if opp_id:
935 delete_in_process = True
936 outdata = {"_id": opp_id}
937 cherrypy.response.status = HTTPStatus.ACCEPTED.value
938 if not delete_in_process:
tierno65ca36d2019-02-12 19:27:52 +0100939 self.engine.del_item(session, engine_topic, _id)
tierno09c073e2018-04-26 13:36:48 +0200940 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tierno55ba2e62018-12-11 17:22:22 +0000941 if engine_topic in ("vim_accounts", "wim_accounts", "sdns"):
tierno09c073e2018-04-26 13:36:48 +0200942 cherrypy.response.status = HTTPStatus.ACCEPTED.value
943
tierno7ae10112018-05-18 14:36:02 +0200944 elif method in ("PUT", "PATCH"):
tiernocd54a4a2018-09-12 16:40:35 +0200945 outdata = None
tierno65ca36d2019-02-12 19:27:52 +0100946 if not indata and not kwargs and not session.get("set_project"):
tiernoc94c3df2018-02-09 15:38:54 +0100947 raise NbiException("Nothing to update. Provide payload and/or query string",
948 HTTPStatus.BAD_REQUEST)
garciadeblas9750c5a2018-10-15 16:20:35 +0200949 if item in ("nsd_content", "package_content", "nst_content") and method == "PUT":
tiernob24258a2018-10-04 18:39:49 +0200950 completed = self.engine.upload_content(session, engine_topic, _id, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +0100951 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +0100952 if not completed:
953 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +0100954 else:
tierno65ca36d2019-02-12 19:27:52 +0100955 self.engine.edit_item(session, engine_topic, _id, indata, kwargs)
tiernocd54a4a2018-09-12 16:40:35 +0200956 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernoc94c3df2018-02-09 15:38:54 +0100957 else:
958 raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tiernof27c79b2018-03-12 17:08:42 +0100959 return self._format_out(outdata, session, _format)
tiernob24258a2018-10-04 18:39:49 +0200960 except Exception as e:
tierno36ec8602018-11-02 17:27:11 +0100961 if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException,
962 ValidationError)):
tiernob24258a2018-10-04 18:39:49 +0200963 http_code_value = cherrypy.response.status = e.http_code.value
964 http_code_name = e.http_code.name
965 cherrypy.log("Exception {}".format(e))
966 else:
967 http_code_value = cherrypy.response.status = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Felipe Vicense36ab852018-11-23 14:12:09 +0100968 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
tiernob24258a2018-10-04 18:39:49 +0200969 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +0200970 if hasattr(outdata, "close"): # is an open file
971 outdata.close()
tiernob24258a2018-10-04 18:39:49 +0200972 error_text = str(e)
973 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +0200974 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +0200975 try:
tiernocc103432018-10-19 14:10:35 +0200976 if rollback_item.get("operation") == "set":
977 self.engine.db.set_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
978 rollback_item["content"], fail_on_empty=False)
979 else:
tiernoe8631782018-12-21 13:31:52 +0000980 self.engine.db.del_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
981 fail_on_empty=False)
tierno3ace63c2018-05-03 17:51:43 +0200982 except Exception as e2:
tiernob24258a2018-10-04 18:39:49 +0200983 rollback_error_text = "Rollback Exception {}: {}".format(rollback_item, e2)
984 cherrypy.log(rollback_error_text)
985 error_text += ". " + rollback_error_text
986 # if isinstance(e, MsgException):
987 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
988 # engine_topic[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +0100989 problem_details = {
tiernob24258a2018-10-04 18:39:49 +0200990 "code": http_code_name,
991 "status": http_code_value,
992 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +0100993 }
994 return self._format_out(problem_details, session)
995 # raise cherrypy.HTTPError(e.http_code.value, str(e))
996
997
tiernoc94c3df2018-02-09 15:38:54 +0100998def _start_service():
999 """
1000 Callback function called when cherrypy.engine starts
1001 Override configuration with env variables
1002 Set database, storage, message configuration
1003 Init database with admin/admin user password
1004 """
tierno932499c2019-01-28 17:28:10 +00001005 global nbi_server
1006 global subscription_thread
tiernoc94c3df2018-02-09 15:38:54 +01001007 cherrypy.log.error("Starting osm_nbi")
1008 # update general cherrypy configuration
1009 update_dict = {}
1010
1011 engine_config = cherrypy.tree.apps['/osm'].config
1012 for k, v in environ.items():
1013 if not k.startswith("OSMNBI_"):
1014 continue
tiernoe1281182018-05-22 12:24:36 +02001015 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +01001016 if not k2:
1017 continue
1018 try:
1019 # update static configuration
1020 if k == 'OSMNBI_STATIC_DIR':
1021 engine_config["/static"]['tools.staticdir.dir'] = v
1022 engine_config["/static"]['tools.staticdir.on'] = True
1023 elif k == 'OSMNBI_SOCKET_PORT' or k == 'OSMNBI_SERVER_PORT':
1024 update_dict['server.socket_port'] = int(v)
1025 elif k == 'OSMNBI_SOCKET_HOST' or k == 'OSMNBI_SERVER_HOST':
1026 update_dict['server.socket_host'] = v
tiernof5298be2018-05-16 14:43:57 +02001027 elif k1 in ("server", "test", "auth", "log"):
1028 update_dict[k1 + '.' + k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001029 elif k1 in ("message", "database", "storage", "authentication"):
tiernof5298be2018-05-16 14:43:57 +02001030 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001031 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +01001032 engine_config[k1][k2] = int(v)
1033 else:
1034 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001035
tiernoc94c3df2018-02-09 15:38:54 +01001036 except ValueError as e:
1037 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1038 except Exception as e:
1039 cherrypy.log.warn("skipping environ '{}' on exception '{}'".format(k, e))
1040
1041 if update_dict:
1042 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +02001043 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001044
1045 # logging cherrypy
1046 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1047 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
1048 logger_server = logging.getLogger("cherrypy.error")
1049 logger_access = logging.getLogger("cherrypy.access")
1050 logger_cherry = logging.getLogger("cherrypy")
1051 logger_nbi = logging.getLogger("nbi")
1052
tiernof5298be2018-05-16 14:43:57 +02001053 if "log.file" in engine_config["global"]:
1054 file_handler = logging.handlers.RotatingFileHandler(engine_config["global"]["log.file"],
tiernoc94c3df2018-02-09 15:38:54 +01001055 maxBytes=100e6, backupCount=9, delay=0)
1056 file_handler.setFormatter(log_formatter_simple)
1057 logger_cherry.addHandler(file_handler)
1058 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +02001059 # log always to standard output
1060 for format_, logger in {"nbi.server %(filename)s:%(lineno)s": logger_server,
1061 "nbi.access %(filename)s:%(lineno)s": logger_access,
1062 "%(name)s %(filename)s:%(lineno)s": logger_nbi
1063 }.items():
1064 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
1065 log_formatter_cherry = logging.Formatter(log_format_cherry, datefmt='%Y-%m-%dT%H:%M:%S')
1066 str_handler = logging.StreamHandler()
1067 str_handler.setFormatter(log_formatter_cherry)
1068 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +01001069
tiernof5298be2018-05-16 14:43:57 +02001070 if engine_config["global"].get("log.level"):
1071 logger_cherry.setLevel(engine_config["global"]["log.level"])
1072 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +01001073
1074 # logging other modules
1075 for k1, logname in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
1076 engine_config[k1]["logger_name"] = logname
1077 logger_module = logging.getLogger(logname)
1078 if "logfile" in engine_config[k1]:
1079 file_handler = logging.handlers.RotatingFileHandler(engine_config[k1]["logfile"],
tierno2236d202018-05-16 19:05:16 +02001080 maxBytes=100e6, backupCount=9, delay=0)
tiernoc94c3df2018-02-09 15:38:54 +01001081 file_handler.setFormatter(log_formatter_simple)
1082 logger_module.addHandler(file_handler)
1083 if "loglevel" in engine_config[k1]:
1084 logger_module.setLevel(engine_config[k1]["loglevel"])
1085 # TODO add more entries, e.g.: storage
1086 cherrypy.tree.apps['/osm'].root.engine.start(engine_config)
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001087 cherrypy.tree.apps['/osm'].root.authenticator.start(engine_config)
tiernod985a8d2018-10-19 14:12:28 +02001088 cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version)
1089 cherrypy.tree.apps['/osm'].root.authenticator.init_db(target_version=auth_database_version)
tiernobee508e2019-01-21 11:21:49 +00001090
tierno932499c2019-01-28 17:28:10 +00001091 # start subscriptions thread:
1092 subscription_thread = SubscriptionThread(config=engine_config, engine=nbi_server.engine)
1093 subscription_thread.start()
1094 # Do not capture except SubscriptionException
1095
tiernobee508e2019-01-21 11:21:49 +00001096 # load and print version. Ignore possible errors, e.g. file not found
1097 try:
1098 with open("{}/version".format(engine_config["/static"]['tools.staticdir.dir'])) as version_file:
1099 version_data = version_file.read()
1100 cherrypy.log.error("Starting OSM NBI Version: {}".format(version_data.replace("\n", " ")))
1101 except Exception:
1102 pass
tiernoc94c3df2018-02-09 15:38:54 +01001103
1104
1105def _stop_service():
1106 """
1107 Callback function called when cherrypy.engine stops
1108 TODO: Ending database connections.
1109 """
tierno932499c2019-01-28 17:28:10 +00001110 global subscription_thread
tierno65ca36d2019-02-12 19:27:52 +01001111 if subscription_thread:
1112 subscription_thread.terminate()
tierno932499c2019-01-28 17:28:10 +00001113 subscription_thread = None
tiernoc94c3df2018-02-09 15:38:54 +01001114 cherrypy.tree.apps['/osm'].root.engine.stop()
1115 cherrypy.log.error("Stopping osm_nbi")
1116
tierno2236d202018-05-16 19:05:16 +02001117
tiernof5298be2018-05-16 14:43:57 +02001118def nbi(config_file):
tierno932499c2019-01-28 17:28:10 +00001119 global nbi_server
tiernoc94c3df2018-02-09 15:38:54 +01001120 # conf = {
1121 # '/': {
1122 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1123 # 'tools.sessions.on': True,
1124 # 'tools.response_headers.on': True,
1125 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1126 # }
1127 # }
1128 # cherrypy.Server.ssl_module = 'builtin'
1129 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1130 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1131 # cherrypy.Server.thread_pool = 10
1132 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1133
1134 # cherrypy.config.update({'tools.auth_basic.on': True,
1135 # 'tools.auth_basic.realm': 'localhost',
1136 # 'tools.auth_basic.checkpassword': validate_password})
tierno932499c2019-01-28 17:28:10 +00001137 nbi_server = Server()
tiernoc94c3df2018-02-09 15:38:54 +01001138 cherrypy.engine.subscribe('start', _start_service)
1139 cherrypy.engine.subscribe('stop', _stop_service)
tierno932499c2019-01-28 17:28:10 +00001140 cherrypy.quickstart(nbi_server, '/osm', config_file)
tiernof5298be2018-05-16 14:43:57 +02001141
1142
1143def usage():
1144 print("""Usage: {} [options]
1145 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1146 -h|--help: shows this help
1147 """.format(sys.argv[0]))
tierno2236d202018-05-16 19:05:16 +02001148 # --log-socket-host HOST: send logs to this host")
1149 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +01001150
1151
1152if __name__ == '__main__':
tiernof5298be2018-05-16 14:43:57 +02001153 try:
1154 # load parameters and configuration
1155 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
1156 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1157 config_file = None
1158 for o, a in opts:
1159 if o in ("-h", "--help"):
1160 usage()
1161 sys.exit()
1162 elif o in ("-c", "--config"):
1163 config_file = a
1164 # elif o == "--log-socket-port":
1165 # log_socket_port = a
1166 # elif o == "--log-socket-host":
1167 # log_socket_host = a
1168 # elif o == "--log-file":
1169 # log_file = a
1170 else:
1171 assert False, "Unhandled option"
1172 if config_file:
1173 if not path.isfile(config_file):
1174 print("configuration file '{}' that not exist".format(config_file), file=sys.stderr)
1175 exit(1)
1176 else:
1177 for config_file in (__file__[:__file__.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
1178 if path.isfile(config_file):
1179 break
1180 else:
1181 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys.stderr)
1182 exit(1)
1183 nbi(config_file)
1184 except getopt.GetoptError as e:
1185 print(str(e), file=sys.stderr)
1186 # usage()
1187 exit(1)