blob: 269af3325c78810c1a7dc7b3757579c62bd3cb7d [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 "<ID>": {"METHODS": ("GET", "DELETE", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200221 },
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100222 "roles": {"METHODS": ("GET", "POST"),
Eduardo Sousab6a58192019-06-04 14:10:15 +0100223 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PUT")}
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100224 },
tierno0f98af52018-03-19 10:28:22 +0100225 "vims": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200226 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200227 },
tierno09c073e2018-04-26 13:36:48 +0200228 "vim_accounts": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200229 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200230 },
tierno55ba2e62018-12-11 17:22:22 +0000231 "wim_accounts": {"METHODS": ("GET", "POST"),
232 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
233 },
tierno0f98af52018-03-19 10:28:22 +0100234 "sdns": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200235 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200236 },
tiernof27c79b2018-03-12 17:08:42 +0100237 }
238 },
tiernocb83c942018-09-24 17:28:13 +0200239 "pdu": {
240 "v1": {
241 "pdu_descriptors": {"METHODS": ("GET", "POST"),
242 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT")}
243 },
244 }
245 },
tiernof27c79b2018-03-12 17:08:42 +0100246 "nsd": {
247 "v1": {
tierno2236d202018-05-16 19:05:16 +0200248 "ns_descriptors_content": {"METHODS": ("GET", "POST"),
249 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
250 },
251 "ns_descriptors": {"METHODS": ("GET", "POST"),
tierno36ec8602018-11-02 17:27:11 +0100252 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
tierno2236d202018-05-16 19:05:16 +0200253 "nsd_content": {"METHODS": ("GET", "PUT")},
254 "nsd": {"METHODS": "GET"}, # descriptor inside package
255 "artifacts": {"*": {"METHODS": "GET"}}
256 }
257 },
tiernof27c79b2018-03-12 17:08:42 +0100258 "pnf_descriptors": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200259 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
260 "pnfd_content": {"TODO": ("GET", "PUT")}
261 }
262 },
tiernof27c79b2018-03-12 17:08:42 +0100263 "subscriptions": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200264 "<ID>": {"TODO": ("GET", "DELETE")}
265 },
tiernof27c79b2018-03-12 17:08:42 +0100266 }
267 },
268 "vnfpkgm": {
269 "v1": {
tierno2236d202018-05-16 19:05:16 +0200270 "vnf_packages_content": {"METHODS": ("GET", "POST"),
271 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
272 },
273 "vnf_packages": {"METHODS": ("GET", "POST"),
tiernoaae4dc42018-06-11 18:55:50 +0200274 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
tierno2236d202018-05-16 19:05:16 +0200275 "package_content": {"METHODS": ("GET", "PUT"), # package
276 "upload_from_uri": {"TODO": "POST"}
277 },
278 "vnfd": {"METHODS": "GET"}, # descriptor inside package
279 "artifacts": {"*": {"METHODS": "GET"}}
280 }
281 },
tiernof27c79b2018-03-12 17:08:42 +0100282 "subscriptions": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200283 "<ID>": {"TODO": ("GET", "DELETE")}
284 },
tiernof27c79b2018-03-12 17:08:42 +0100285 }
286 },
287 "nslcm": {
288 "v1": {
289 "ns_instances_content": {"METHODS": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200290 "<ID>": {"METHODS": ("GET", "DELETE")}
291 },
tierno65acb4d2018-04-06 16:42:40 +0200292 "ns_instances": {"METHODS": ("GET", "POST"),
tierno95692442018-05-24 18:05:28 +0200293 "<ID>": {"METHODS": ("GET", "DELETE"),
tiernof759d822018-06-11 18:54:54 +0200294 "scale": {"METHODS": "POST"},
tierno2236d202018-05-16 19:05:16 +0200295 "terminate": {"METHODS": "POST"},
296 "instantiate": {"METHODS": "POST"},
297 "action": {"METHODS": "POST"},
298 }
299 },
tierno65acb4d2018-04-06 16:42:40 +0200300 "ns_lcm_op_occs": {"METHODS": "GET",
tierno2236d202018-05-16 19:05:16 +0200301 "<ID>": {"METHODS": "GET"},
302 },
tierno0ffaa992018-05-09 13:21:56 +0200303 "vnfrs": {"METHODS": ("GET"),
tierno2236d202018-05-16 19:05:16 +0200304 "<ID>": {"METHODS": ("GET")}
305 },
tiernof759d822018-06-11 18:54:54 +0200306 "vnf_instances": {"METHODS": ("GET"),
307 "<ID>": {"METHODS": ("GET")}
308 },
tiernof27c79b2018-03-12 17:08:42 +0100309 }
310 },
garciadeblas9750c5a2018-10-15 16:20:35 +0200311 "nst": {
312 "v1": {
313 "netslice_templates_content": {"METHODS": ("GET", "POST"),
314 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
315 },
316 "netslice_templates": {"METHODS": ("GET", "POST"),
317 "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH",
318 "nst_content": {"METHODS": ("GET", "PUT")},
319 "nst": {"METHODS": "GET"}, # descriptor inside package
320 "artifacts": {"*": {"METHODS": "GET"}}
321 }
322 },
323 "subscriptions": {"TODO": ("GET", "POST"),
324 "<ID>": {"TODO": ("GET", "DELETE")}
325 },
326 }
327 },
328 "nsilcm": {
329 "v1": {
330 "netslice_instances_content": {"METHODS": ("GET", "POST"),
331 "<ID>": {"METHODS": ("GET", "DELETE")}
332 },
333 "netslice_instances": {"METHODS": ("GET", "POST"),
334 "<ID>": {"METHODS": ("GET", "DELETE"),
335 "terminate": {"METHODS": "POST"},
336 "instantiate": {"METHODS": "POST"},
337 "action": {"METHODS": "POST"},
338 }
339 },
340 "nsi_lcm_op_occs": {"METHODS": "GET",
341 "<ID>": {"METHODS": "GET"},
342 },
343 }
344 },
vijay.r35ef2f72019-04-30 17:55:49 +0530345 "nspm": {
346 "v1": {
347 "pm_jobs": {
348 "<ID>": {
349 "reports": {
350 "<ID>": {"METHODS": ("GET")}
351 }
352 },
353 },
354 },
355 },
tiernof27c79b2018-03-12 17:08:42 +0100356 }
tiernoc94c3df2018-02-09 15:38:54 +0100357
tiernoc94c3df2018-02-09 15:38:54 +0100358 def _format_in(self, kwargs):
359 try:
360 indata = None
361 if cherrypy.request.body.length:
362 error_text = "Invalid input format "
363
364 if "Content-Type" in cherrypy.request.headers:
365 if "application/json" in cherrypy.request.headers["Content-Type"]:
366 error_text = "Invalid json format "
367 indata = json.load(self.reader(cherrypy.request.body))
gcalvinode4adfe2018-10-30 11:46:09 +0100368 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100369 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
370 error_text = "Invalid yaml format "
371 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100372 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100373 elif "application/binary" in cherrypy.request.headers["Content-Type"] or \
374 "application/gzip" in cherrypy.request.headers["Content-Type"] or \
tiernof27c79b2018-03-12 17:08:42 +0100375 "application/zip" in cherrypy.request.headers["Content-Type"] or \
376 "text/plain" in cherrypy.request.headers["Content-Type"]:
377 indata = cherrypy.request.body # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100378 elif "multipart/form-data" in cherrypy.request.headers["Content-Type"]:
379 if "descriptor_file" in kwargs:
380 filecontent = kwargs.pop("descriptor_file")
381 if not filecontent.file:
382 raise NbiException("empty file or content", HTTPStatus.BAD_REQUEST)
tiernof27c79b2018-03-12 17:08:42 +0100383 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100384 if filecontent.content_type.value:
385 cherrypy.request.headers["Content-Type"] = filecontent.content_type.value
386 else:
387 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
388 # "Only 'Content-Type' of type 'application/json' or
389 # 'application/yaml' for input format are available")
390 error_text = "Invalid yaml format "
391 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100392 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100393 else:
394 error_text = "Invalid yaml format "
395 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100396 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100397 if not indata:
398 indata = {}
399
tiernoc94c3df2018-02-09 15:38:54 +0100400 format_yaml = False
401 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
402 format_yaml = True
403
404 for k, v in kwargs.items():
405 if isinstance(v, str):
406 if v == "":
407 kwargs[k] = None
408 elif format_yaml:
409 try:
410 kwargs[k] = yaml.load(v)
tiernoe1281182018-05-22 12:24:36 +0200411 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100412 pass
413 elif k.endswith(".gt") or k.endswith(".lt") or k.endswith(".gte") or k.endswith(".lte"):
414 try:
415 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200416 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100417 try:
418 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200419 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100420 pass
421 elif v.find(",") > 0:
422 kwargs[k] = v.split(",")
423 elif isinstance(v, (list, tuple)):
424 for index in range(0, len(v)):
425 if v[index] == "":
426 v[index] = None
427 elif format_yaml:
428 try:
429 v[index] = yaml.load(v[index])
tiernoe1281182018-05-22 12:24:36 +0200430 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100431 pass
432
tiernof27c79b2018-03-12 17:08:42 +0100433 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100434 except (ValueError, yaml.YAMLError) as exc:
435 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
436 except KeyError as exc:
437 raise NbiException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
tiernob92094f2018-05-11 13:44:22 +0200438 except Exception as exc:
439 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100440
441 @staticmethod
tiernof27c79b2018-03-12 17:08:42 +0100442 def _format_out(data, session=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100443 """
444 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100445 :param data: response to be sent. Can be a dict, text or file
tiernoc94c3df2018-02-09 15:38:54 +0100446 :param session:
tiernof27c79b2018-03-12 17:08:42 +0100447 :param _format: The format to be set as Content-Type ir data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100448 :return: None
449 """
tierno0f98af52018-03-19 10:28:22 +0100450 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100451 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100452 if accept and "text/html" in accept:
453 return html.format(data, cherrypy.request, cherrypy.response, session)
tierno09c073e2018-04-26 13:36:48 +0200454 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100455 return
456 elif hasattr(data, "read"): # file object
457 if _format:
458 cherrypy.response.headers["Content-Type"] = _format
459 elif "b" in data.mode: # binariy asssumig zip
460 cherrypy.response.headers["Content-Type"] = 'application/zip'
461 else:
462 cherrypy.response.headers["Content-Type"] = 'text/plain'
463 # TODO check that cherrypy close file. If not implement pending things to close per thread next
464 return data
tierno0f98af52018-03-19 10:28:22 +0100465 if accept:
tiernoc94c3df2018-02-09 15:38:54 +0100466 if "application/json" in accept:
467 cherrypy.response.headers["Content-Type"] = 'application/json; charset=utf-8'
468 a = json.dumps(data, indent=4) + "\n"
469 return a.encode("utf8")
470 elif "text/html" in accept:
471 return html.format(data, cherrypy.request, cherrypy.response, session)
472
tiernof27c79b2018-03-12 17:08:42 +0100473 elif "application/yaml" in accept or "*/*" in accept or "text/plain" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100474 pass
tiernof717cbe2018-12-03 16:35:42 +0000475 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
476 elif cherrypy.response.status >= 400:
tiernoc94c3df2018-02-09 15:38:54 +0100477 raise cherrypy.HTTPError(HTTPStatus.NOT_ACCEPTABLE.value,
478 "Only 'Accept' of type 'application/json' or 'application/yaml' "
479 "for output format are available")
480 cherrypy.response.headers["Content-Type"] = 'application/yaml'
481 return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False,
482 encoding='utf-8', allow_unicode=True) # , canonical=True, default_style='"'
483
484 @cherrypy.expose
485 def index(self, *args, **kwargs):
486 session = None
487 try:
488 if cherrypy.request.method == "GET":
Eduardo Sousa2f988212018-07-26 01:04:11 +0100489 session = self.authenticator.authorize()
tiernoc94c3df2018-02-09 15:38:54 +0100490 outdata = "Index page"
491 else:
492 raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value,
tierno2236d202018-05-16 19:05:16 +0200493 "Method {} not allowed for tokens".format(cherrypy.request.method))
tiernoc94c3df2018-02-09 15:38:54 +0100494
495 return self._format_out(outdata, session)
496
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100497 except (EngineException, AuthException) as e:
tiernoc94c3df2018-02-09 15:38:54 +0100498 cherrypy.log("index Exception {}".format(e))
499 cherrypy.response.status = e.http_code.value
500 return self._format_out("Welcome to OSM!", session)
501
502 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200503 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200504 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200505 global __version__, version_date
506 try:
507 if cherrypy.request.method != "GET":
508 raise NbiException("Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED)
509 elif args or kwargs:
510 raise NbiException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED)
511 return __version__ + " " + version_date
512 except NbiException as e:
513 cherrypy.response.status = e.http_code.value
514 problem_details = {
515 "code": e.http_code.name,
516 "status": e.http_code.value,
517 "detail": str(e),
518 }
519 return self._format_out(problem_details, None)
520
521 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100522 def token(self, method, token_id=None, kwargs=None):
tiernoc94c3df2018-02-09 15:38:54 +0100523 session = None
524 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +0100525 indata = self._format_in(kwargs)
526 if not isinstance(indata, dict):
527 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100528 try:
tiernoc94c3df2018-02-09 15:38:54 +0100529 if method == "GET":
Eduardo Sousa2f988212018-07-26 01:04:11 +0100530 session = self.authenticator.authorize()
tiernof27c79b2018-03-12 17:08:42 +0100531 if token_id:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100532 outdata = self.authenticator.get_token(session, token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100533 else:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100534 outdata = self.authenticator.get_token_list(session)
tiernoc94c3df2018-02-09 15:38:54 +0100535 elif method == "POST":
536 try:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100537 session = self.authenticator.authorize()
tiernoe1281182018-05-22 12:24:36 +0200538 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100539 session = None
540 if kwargs:
541 indata.update(kwargs)
Eduardo Sousa2f988212018-07-26 01:04:11 +0100542 outdata = self.authenticator.new_token(session, indata, cherrypy.request.remote)
tiernoc94c3df2018-02-09 15:38:54 +0100543 session = outdata
544 cherrypy.session['Authorization'] = outdata["_id"]
tierno0f98af52018-03-19 10:28:22 +0100545 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
tiernoc94c3df2018-02-09 15:38:54 +0100546 # cherrypy.response.cookie["Authorization"] = outdata["id"]
547 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
548 elif method == "DELETE":
tiernof27c79b2018-03-12 17:08:42 +0100549 if not token_id and "id" in kwargs:
tiernoc94c3df2018-02-09 15:38:54 +0100550 token_id = kwargs["id"]
tiernof27c79b2018-03-12 17:08:42 +0100551 elif not token_id:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100552 session = self.authenticator.authorize()
tiernoc94c3df2018-02-09 15:38:54 +0100553 token_id = session["_id"]
Eduardo Sousa2f988212018-07-26 01:04:11 +0100554 outdata = self.authenticator.del_token(token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100555 session = None
556 cherrypy.session['Authorization'] = "logout"
557 # cherrypy.response.cookie["Authorization"] = token_id
558 # cherrypy.response.cookie["Authorization"]['expires'] = 0
559 else:
560 raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
561 return self._format_out(outdata, session)
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100562 except (NbiException, EngineException, DbException, AuthException) as e:
tiernoc94c3df2018-02-09 15:38:54 +0100563 cherrypy.log("tokens Exception {}".format(e))
564 cherrypy.response.status = e.http_code.value
565 problem_details = {
566 "code": e.http_code.name,
567 "status": e.http_code.value,
568 "detail": str(e),
569 }
570 return self._format_out(problem_details, session)
571
572 @cherrypy.expose
573 def test(self, *args, **kwargs):
574 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +0100575 if args and args[0] == "help":
tiernof717cbe2018-12-03 16:35:42 +0000576 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
tiernoe1281182018-05-22 12:24:36 +0200577 "sleep/<time>\nmessage/topic\n</pre></html>"
tiernof27c79b2018-03-12 17:08:42 +0100578
579 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +0100580 try:
581 # self.engine.load_dbase(cherrypy.request.app.config)
582 self.engine.create_admin()
583 return "Done. User 'admin', password 'admin' created"
584 except Exception:
585 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
586 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +0100587 elif args and args[0] == "file":
588 return cherrypy.lib.static.serve_file(cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1],
589 "text/plain", "attachment")
590 elif args and args[0] == "file2":
591 f_path = cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1]
592 f = open(f_path, "r")
593 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100594 return f
tierno55945e72018-04-06 16:40:27 +0200595
tiernof27c79b2018-03-12 17:08:42 +0100596 elif len(args) == 2 and args[0] == "db-clear":
tiernof717cbe2018-12-03 16:35:42 +0000597 deleted_info = self.engine.db.del_list(args[1], kwargs)
598 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
599 elif len(args) and args[0] == "fs-clear":
600 if len(args) >= 2:
601 folders = (args[1],)
602 else:
603 folders = self.engine.fs.dir_ls(".")
604 for folder in folders:
605 self.engine.fs.file_delete(folder)
606 return ",".join(folders) + " folders deleted\n"
tiernoc94c3df2018-02-09 15:38:54 +0100607 elif args and args[0] == "login":
608 if not cherrypy.request.headers.get("Authorization"):
609 cherrypy.response.headers["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
610 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
611 elif args and args[0] == "login2":
612 if not cherrypy.request.headers.get("Authorization"):
613 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
614 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
615 elif args and args[0] == "sleep":
616 sleep_time = 5
617 try:
618 sleep_time = int(args[1])
619 except Exception:
620 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
621 return self._format_out("Database already initialized")
622 thread_info = cherrypy.thread_data
623 print(thread_info)
624 time.sleep(sleep_time)
625 # thread_info
626 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +0200627 main_topic = args[1]
628 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +0100629 try:
tierno55945e72018-04-06 16:40:27 +0200630 if cherrypy.request.method == 'POST':
631 to_send = yaml.load(cherrypy.request.body)
632 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +0200633 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +0200634 return_text += " {}: {}\n".format(k, v)
635 elif cherrypy.request.method == 'GET':
636 for k, v in kwargs.items():
tiernob24258a2018-10-04 18:39:49 +0200637 self.engine.msg.write(main_topic, k, yaml.load(v))
tierno55945e72018-04-06 16:40:27 +0200638 return_text += " {}: {}\n".format(k, yaml.load(v))
tiernoc94c3df2018-02-09 15:38:54 +0100639 except Exception as e:
tierno55945e72018-04-06 16:40:27 +0200640 return_text += "Error: " + str(e)
641 return_text += "</pre></html>\n"
642 return return_text
tiernoc94c3df2018-02-09 15:38:54 +0100643
644 return_text = (
645 "<html><pre>\nheaders:\n args: {}\n".format(args) +
646 " kwargs: {}\n".format(kwargs) +
647 " headers: {}\n".format(cherrypy.request.headers) +
648 " path_info: {}\n".format(cherrypy.request.path_info) +
649 " query_string: {}\n".format(cherrypy.request.query_string) +
650 " session: {}\n".format(cherrypy.session) +
651 " cookie: {}\n".format(cherrypy.request.cookie) +
652 " method: {}\n".format(cherrypy.request.method) +
tiernof717cbe2018-12-03 16:35:42 +0000653 " session: {}\n".format(cherrypy.session.get('fieldname')) +
tiernoc94c3df2018-02-09 15:38:54 +0100654 " body:\n")
655 return_text += " length: {}\n".format(cherrypy.request.body.length)
656 if cherrypy.request.body.length:
657 return_text += " content: {}\n".format(
658 str(cherrypy.request.body.read(int(cherrypy.request.headers.get('Content-Length', 0)))))
659 if thread_info:
660 return_text += "thread: {}\n".format(thread_info)
661 return_text += "</pre></html>"
662 return return_text
663
tiernof27c79b2018-03-12 17:08:42 +0100664 def _check_valid_url_method(self, method, *args):
665 if len(args) < 3:
tiernob24258a2018-10-04 18:39:49 +0200666 raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus.METHOD_NOT_ALLOWED)
tiernof27c79b2018-03-12 17:08:42 +0100667
668 reference = self.valid_methods
669 for arg in args:
670 if arg is None:
671 break
672 if not isinstance(reference, dict):
673 raise NbiException("URL contains unexpected extra items '{}'".format(arg),
674 HTTPStatus.METHOD_NOT_ALLOWED)
675
676 if arg in reference:
677 reference = reference[arg]
678 elif "<ID>" in reference:
679 reference = reference["<ID>"]
680 elif "*" in reference:
681 reference = reference["*"]
682 break
683 else:
684 raise NbiException("Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED)
685 if "TODO" in reference and method in reference["TODO"]:
686 raise NbiException("Method {} not supported yet for this URL".format(method), HTTPStatus.NOT_IMPLEMENTED)
tierno2236d202018-05-16 19:05:16 +0200687 elif "METHODS" in reference and method not in reference["METHODS"]:
tiernof27c79b2018-03-12 17:08:42 +0100688 raise NbiException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
689 return
690
691 @staticmethod
tiernob24258a2018-10-04 18:39:49 +0200692 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +0100693 """
694 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +0200695 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +0100696 :param version:
tiernob24258a2018-10-04 18:39:49 +0200697 :param topic:
tiernof27c79b2018-03-12 17:08:42 +0100698 :param id:
699 :return: None
700 """
701 # 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 +0200702 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(main_topic, version, topic, id)
tiernof27c79b2018-03-12 17:08:42 +0100703 return
704
tierno65ca36d2019-02-12 19:27:52 +0100705 @staticmethod
706 def _manage_admin_query(session, kwargs, method, _id):
707 """
708 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
709 Check that users has rights to use them and returs the admin_query
710 :param session: session rights obtained by token
711 :param kwargs: query string input.
712 :param method: http method: GET, POSST, PUT, ...
713 :param _id:
714 :return: admin_query dictionary with keys:
715 public: True, False or None
716 force: True or False
717 project_id: tuple with projects used for accessing an element
718 set_project: tuple with projects that a created element will belong to
719 method: show, list, delete, write
720 """
721 admin_query = {"force": False, "project_id": (session["project_id"], ), "username": session["username"],
722 "admin": session["admin"], "public": None}
723 if kwargs:
724 # FORCE
725 if "FORCE" in kwargs:
726 if kwargs["FORCE"].lower() != "false": # if None or True set force to True
727 admin_query["force"] = True
728 del kwargs["FORCE"]
729 # PUBLIC
730 if "PUBLIC" in kwargs:
731 if kwargs["PUBLIC"].lower() != "false": # if None or True set public to True
732 admin_query["public"] = True
733 else:
734 admin_query["public"] = False
735 del kwargs["PUBLIC"]
736 # ADMIN
737 if "ADMIN" in kwargs:
738 behave_as = kwargs.pop("ADMIN")
739 if behave_as.lower() != "false":
740 if not session["admin"]:
741 raise NbiException("Only admin projects can use 'ADMIN' query string", HTTPStatus.UNAUTHORIZED)
742 if not behave_as or behave_as.lower() == "true": # convert True, None to empty list
743 admin_query["project_id"] = ()
744 elif isinstance(behave_as, (list, tuple)):
745 admin_query["project_id"] = behave_as
746 else: # isinstance(behave_as, str)
747 admin_query["project_id"] = (behave_as, )
748 if "SET_PROJECT" in kwargs:
749 set_project = kwargs.pop("SET_PROJECT")
750 if not set_project:
751 admin_query["set_project"] = list(admin_query["project_id"])
752 else:
753 if isinstance(set_project, str):
754 set_project = (set_project, )
755 if admin_query["project_id"]:
756 for p in set_project:
757 if p not in admin_query["project_id"]:
758 raise NbiException("Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
759 "'ADMIN='{p}'".format(p=p), HTTPStatus.UNAUTHORIZED)
760 admin_query["set_project"] = set_project
761
762 # PROJECT_READ
763 # if "PROJECT_READ" in kwargs:
764 # admin_query["project"] = kwargs.pop("project")
765 # if admin_query["project"] == session["project_id"]:
766 if method == "GET":
767 if _id:
768 admin_query["method"] = "show"
769 else:
770 admin_query["method"] = "list"
771 elif method == "DELETE":
772 admin_query["method"] = "delete"
773 else:
774 admin_query["method"] = "write"
775 return admin_query
776
tiernoc94c3df2018-02-09 15:38:54 +0100777 @cherrypy.expose
tiernob24258a2018-10-04 18:39:49 +0200778 def default(self, main_topic=None, version=None, topic=None, _id=None, item=None, *args, **kwargs):
tiernoc94c3df2018-02-09 15:38:54 +0100779 session = None
tiernof27c79b2018-03-12 17:08:42 +0100780 outdata = None
781 _format = None
tierno0f98af52018-03-19 10:28:22 +0100782 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +0200783 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +0200784 rollback = []
785 session = None
tiernoc94c3df2018-02-09 15:38:54 +0100786 try:
tiernob24258a2018-10-04 18:39:49 +0200787 if not main_topic or not version or not topic:
788 raise NbiException("URL must contain at least 'main_topic/version/topic'",
789 HTTPStatus.METHOD_NOT_ALLOWED)
vijay.r35ef2f72019-04-30 17:55:49 +0530790 if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm", "nspm"):
tiernob24258a2018-10-04 18:39:49 +0200791 raise NbiException("URL main_topic '{}' not supported".format(main_topic),
792 HTTPStatus.METHOD_NOT_ALLOWED)
tiernoc94c3df2018-02-09 15:38:54 +0100793 if version != 'v1':
794 raise NbiException("URL version '{}' not supported".format(version), HTTPStatus.METHOD_NOT_ALLOWED)
795
tiernof27c79b2018-03-12 17:08:42 +0100796 if kwargs and "METHOD" in kwargs and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
797 method = kwargs.pop("METHOD")
798 else:
799 method = cherrypy.request.method
tierno65ca36d2019-02-12 19:27:52 +0100800
tiernob24258a2018-10-04 18:39:49 +0200801 self._check_valid_url_method(method, main_topic, version, topic, _id, item, *args)
tierno65ca36d2019-02-12 19:27:52 +0100802
tiernob24258a2018-10-04 18:39:49 +0200803 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +0100804 return self.token(method, _id, kwargs)
805
tiernoc94c3df2018-02-09 15:38:54 +0100806 # self.engine.load_dbase(cherrypy.request.app.config)
Eduardo Sousa2f988212018-07-26 01:04:11 +0100807 session = self.authenticator.authorize()
tierno65ca36d2019-02-12 19:27:52 +0100808 session = self._manage_admin_query(session, kwargs, method, _id)
tiernof27c79b2018-03-12 17:08:42 +0100809 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +0200810 engine_topic = topic
811 if topic == "subscriptions":
812 engine_topic = main_topic + "_" + topic
vijay.r35ef2f72019-04-30 17:55:49 +0530813 if item and topic != "pm_jobs":
tiernob24258a2018-10-04 18:39:49 +0200814 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +0100815
tiernob24258a2018-10-04 18:39:49 +0200816 if main_topic == "nsd":
817 engine_topic = "nsds"
818 elif main_topic == "vnfpkgm":
819 engine_topic = "vnfds"
820 elif main_topic == "nslcm":
821 engine_topic = "nsrs"
822 if topic == "ns_lcm_op_occs":
823 engine_topic = "nslcmops"
824 if topic == "vnfrs" or topic == "vnf_instances":
825 engine_topic = "vnfrs"
garciadeblas9750c5a2018-10-15 16:20:35 +0200826 elif main_topic == "nst":
827 engine_topic = "nsts"
828 elif main_topic == "nsilcm":
829 engine_topic = "nsis"
830 if topic == "nsi_lcm_op_occs":
delacruzramoc061f562019-04-05 11:00:02 +0200831 engine_topic = "nsilcmops"
tiernob24258a2018-10-04 18:39:49 +0200832 elif main_topic == "pdu":
833 engine_topic = "pdus"
tierno65ca36d2019-02-12 19:27:52 +0100834 if engine_topic == "vims": # TODO this is for backward compatibility, it will be removed in the future
tiernob24258a2018-10-04 18:39:49 +0200835 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +0100836
837 if method == "GET":
Felipe Vicens07f31722018-10-29 15:16:44 +0100838 if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
garciadeblas9750c5a2018-10-15 16:20:35 +0200839 if item in ("vnfd", "nsd", "nst"):
tiernof27c79b2018-03-12 17:08:42 +0100840 path = "$DESCRIPTOR"
841 elif args:
842 path = args
tiernob24258a2018-10-04 18:39:49 +0200843 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +0100844 path = ()
845 else:
846 path = None
tiernob24258a2018-10-04 18:39:49 +0200847 file, _format = self.engine.get_file(session, engine_topic, _id, path,
tierno2236d202018-05-16 19:05:16 +0200848 cherrypy.request.headers.get("Accept"))
tiernof27c79b2018-03-12 17:08:42 +0100849 outdata = file
850 elif not _id:
tiernob24258a2018-10-04 18:39:49 +0200851 outdata = self.engine.get_item_list(session, engine_topic, kwargs)
tiernoc94c3df2018-02-09 15:38:54 +0100852 else:
vijay.r35ef2f72019-04-30 17:55:49 +0530853 if item == "reports":
854 # TODO check that project_id (_id in this context) has permissions
855 _id = args[0]
tiernob24258a2018-10-04 18:39:49 +0200856 outdata = self.engine.get_item(session, engine_topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100857 elif method == "POST":
garciadeblas9750c5a2018-10-15 16:20:35 +0200858 if topic in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"):
tiernof27c79b2018-03-12 17:08:42 +0100859 _id = cherrypy.request.headers.get("Transaction-Id")
860 if not _id:
tierno65ca36d2019-02-12 19:27:52 +0100861 _id = self.engine.new_item(rollback, session, engine_topic, {}, None, cherrypy.request.headers)
tiernob24258a2018-10-04 18:39:49 +0200862 completed = self.engine.upload_content(session, engine_topic, _id, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +0100863 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +0100864 if completed:
tiernob24258a2018-10-04 18:39:49 +0200865 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100866 else:
867 cherrypy.response.headers["Transaction-Id"] = _id
868 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +0200869 elif topic == "ns_instances_content":
870 # creates NSR
tierno65ca36d2019-02-12 19:27:52 +0100871 _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs)
tiernob24258a2018-10-04 18:39:49 +0200872 # creates nslcmop
873 indata["lcmOperationType"] = "instantiate"
874 indata["nsInstanceId"] = _id
kuuse078f55e2019-05-16 19:24:21 +0200875 nslcmop_id = self.engine.new_item(rollback, session, "nslcmops", indata, None)
tiernob24258a2018-10-04 18:39:49 +0200876 self._set_location_header(main_topic, version, topic, _id)
kuuse078f55e2019-05-16 19:24:21 +0200877 outdata = {"id": _id, "nslcmop_id": nslcmop_id}
tiernob24258a2018-10-04 18:39:49 +0200878 elif topic == "ns_instances" and item:
879 indata["lcmOperationType"] = item
880 indata["nsInstanceId"] = _id
881 _id = self.engine.new_item(rollback, session, "nslcmops", indata, kwargs)
882 self._set_location_header(main_topic, version, "ns_lcm_op_occs", _id)
tierno65acb4d2018-04-06 16:42:40 +0200883 outdata = {"id": _id}
884 cherrypy.response.status = HTTPStatus.ACCEPTED.value
garciadeblas9750c5a2018-10-15 16:20:35 +0200885 elif topic == "netslice_instances_content":
Felipe Vicens07f31722018-10-29 15:16:44 +0100886 # creates NetSlice_Instance_record (NSIR)
tierno65ca36d2019-02-12 19:27:52 +0100887 _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs)
Felipe Vicens07f31722018-10-29 15:16:44 +0100888 self._set_location_header(main_topic, version, topic, _id)
garciadeblas9750c5a2018-10-15 16:20:35 +0200889 indata["lcmOperationType"] = "instantiate"
Felipe Vicens126af572019-06-05 19:13:04 +0200890 indata["netsliceInstanceId"] = _id
kuuse078f55e2019-05-16 19:24:21 +0200891 nsilcmop_id = self.engine.new_item(rollback, session, "nsilcmops", indata, kwargs)
892 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
delacruzramoc061f562019-04-05 11:00:02 +0200893
garciadeblas9750c5a2018-10-15 16:20:35 +0200894 elif topic == "netslice_instances" and item:
895 indata["lcmOperationType"] = item
Felipe Vicens126af572019-06-05 19:13:04 +0200896 indata["netsliceInstanceId"] = _id
garciadeblas9750c5a2018-10-15 16:20:35 +0200897 _id = self.engine.new_item(rollback, session, "nsilcmops", indata, kwargs)
898 self._set_location_header(main_topic, version, "nsi_lcm_op_occs", _id)
899 outdata = {"id": _id}
900 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +0100901 else:
tiernob24258a2018-10-04 18:39:49 +0200902 _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +0100903 cherrypy.request.headers)
tiernob24258a2018-10-04 18:39:49 +0200904 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100905 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +0200906 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tiernof27c79b2018-03-12 17:08:42 +0100907 cherrypy.response.status = HTTPStatus.CREATED.value
tierno09c073e2018-04-26 13:36:48 +0200908
tiernoc94c3df2018-02-09 15:38:54 +0100909 elif method == "DELETE":
910 if not _id:
tiernob24258a2018-10-04 18:39:49 +0200911 outdata = self.engine.del_item_list(session, engine_topic, kwargs)
tierno09c073e2018-04-26 13:36:48 +0200912 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +0100913 else: # len(args) > 1
tiernoe8631782018-12-21 13:31:52 +0000914 delete_in_process = False
tierno65ca36d2019-02-12 19:27:52 +0100915 if topic == "ns_instances_content" and not session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200916 nslcmop_desc = {
917 "lcmOperationType": "terminate",
918 "nsInstanceId": _id,
919 "autoremove": True
920 }
921 opp_id = self.engine.new_item(rollback, session, "nslcmops", nslcmop_desc, None)
tiernoe8631782018-12-21 13:31:52 +0000922 if opp_id:
923 delete_in_process = True
924 outdata = {"_id": opp_id}
925 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tierno65ca36d2019-02-12 19:27:52 +0100926 elif topic == "netslice_instances_content" and not session["force"]:
garciadeblas9750c5a2018-10-15 16:20:35 +0200927 nsilcmop_desc = {
928 "lcmOperationType": "terminate",
Felipe Vicens126af572019-06-05 19:13:04 +0200929 "netsliceInstanceId": _id,
garciadeblas9750c5a2018-10-15 16:20:35 +0200930 "autoremove": True
931 }
932 opp_id = self.engine.new_item(rollback, session, "nsilcmops", nsilcmop_desc, None)
tiernoe8631782018-12-21 13:31:52 +0000933 if opp_id:
934 delete_in_process = True
935 outdata = {"_id": opp_id}
936 cherrypy.response.status = HTTPStatus.ACCEPTED.value
937 if not delete_in_process:
tierno65ca36d2019-02-12 19:27:52 +0100938 self.engine.del_item(session, engine_topic, _id)
tierno09c073e2018-04-26 13:36:48 +0200939 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tierno55ba2e62018-12-11 17:22:22 +0000940 if engine_topic in ("vim_accounts", "wim_accounts", "sdns"):
tierno09c073e2018-04-26 13:36:48 +0200941 cherrypy.response.status = HTTPStatus.ACCEPTED.value
942
tierno7ae10112018-05-18 14:36:02 +0200943 elif method in ("PUT", "PATCH"):
tiernocd54a4a2018-09-12 16:40:35 +0200944 outdata = None
tierno65ca36d2019-02-12 19:27:52 +0100945 if not indata and not kwargs and not session.get("set_project"):
tiernoc94c3df2018-02-09 15:38:54 +0100946 raise NbiException("Nothing to update. Provide payload and/or query string",
947 HTTPStatus.BAD_REQUEST)
garciadeblas9750c5a2018-10-15 16:20:35 +0200948 if item in ("nsd_content", "package_content", "nst_content") and method == "PUT":
tiernob24258a2018-10-04 18:39:49 +0200949 completed = self.engine.upload_content(session, engine_topic, _id, indata, kwargs,
tierno65ca36d2019-02-12 19:27:52 +0100950 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +0100951 if not completed:
952 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +0100953 else:
tierno65ca36d2019-02-12 19:27:52 +0100954 self.engine.edit_item(session, engine_topic, _id, indata, kwargs)
tiernocd54a4a2018-09-12 16:40:35 +0200955 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernoc94c3df2018-02-09 15:38:54 +0100956 else:
957 raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tiernof27c79b2018-03-12 17:08:42 +0100958 return self._format_out(outdata, session, _format)
tiernob24258a2018-10-04 18:39:49 +0200959 except Exception as e:
tierno36ec8602018-11-02 17:27:11 +0100960 if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException,
961 ValidationError)):
tiernob24258a2018-10-04 18:39:49 +0200962 http_code_value = cherrypy.response.status = e.http_code.value
963 http_code_name = e.http_code.name
964 cherrypy.log("Exception {}".format(e))
965 else:
966 http_code_value = cherrypy.response.status = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Felipe Vicense36ab852018-11-23 14:12:09 +0100967 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
tiernob24258a2018-10-04 18:39:49 +0200968 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +0200969 if hasattr(outdata, "close"): # is an open file
970 outdata.close()
tiernob24258a2018-10-04 18:39:49 +0200971 error_text = str(e)
972 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +0200973 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +0200974 try:
tiernocc103432018-10-19 14:10:35 +0200975 if rollback_item.get("operation") == "set":
976 self.engine.db.set_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
977 rollback_item["content"], fail_on_empty=False)
978 else:
tiernoe8631782018-12-21 13:31:52 +0000979 self.engine.db.del_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
980 fail_on_empty=False)
tierno3ace63c2018-05-03 17:51:43 +0200981 except Exception as e2:
tiernob24258a2018-10-04 18:39:49 +0200982 rollback_error_text = "Rollback Exception {}: {}".format(rollback_item, e2)
983 cherrypy.log(rollback_error_text)
984 error_text += ". " + rollback_error_text
985 # if isinstance(e, MsgException):
986 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
987 # engine_topic[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +0100988 problem_details = {
tiernob24258a2018-10-04 18:39:49 +0200989 "code": http_code_name,
990 "status": http_code_value,
991 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +0100992 }
993 return self._format_out(problem_details, session)
994 # raise cherrypy.HTTPError(e.http_code.value, str(e))
995
996
tiernoc94c3df2018-02-09 15:38:54 +0100997def _start_service():
998 """
999 Callback function called when cherrypy.engine starts
1000 Override configuration with env variables
1001 Set database, storage, message configuration
1002 Init database with admin/admin user password
1003 """
tierno932499c2019-01-28 17:28:10 +00001004 global nbi_server
1005 global subscription_thread
tiernoc94c3df2018-02-09 15:38:54 +01001006 cherrypy.log.error("Starting osm_nbi")
1007 # update general cherrypy configuration
1008 update_dict = {}
1009
1010 engine_config = cherrypy.tree.apps['/osm'].config
1011 for k, v in environ.items():
1012 if not k.startswith("OSMNBI_"):
1013 continue
tiernoe1281182018-05-22 12:24:36 +02001014 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +01001015 if not k2:
1016 continue
1017 try:
1018 # update static configuration
1019 if k == 'OSMNBI_STATIC_DIR':
1020 engine_config["/static"]['tools.staticdir.dir'] = v
1021 engine_config["/static"]['tools.staticdir.on'] = True
1022 elif k == 'OSMNBI_SOCKET_PORT' or k == 'OSMNBI_SERVER_PORT':
1023 update_dict['server.socket_port'] = int(v)
1024 elif k == 'OSMNBI_SOCKET_HOST' or k == 'OSMNBI_SERVER_HOST':
1025 update_dict['server.socket_host'] = v
tiernof5298be2018-05-16 14:43:57 +02001026 elif k1 in ("server", "test", "auth", "log"):
1027 update_dict[k1 + '.' + k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001028 elif k1 in ("message", "database", "storage", "authentication"):
tiernof5298be2018-05-16 14:43:57 +02001029 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001030 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +01001031 engine_config[k1][k2] = int(v)
1032 else:
1033 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001034
tiernoc94c3df2018-02-09 15:38:54 +01001035 except ValueError as e:
1036 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1037 except Exception as e:
1038 cherrypy.log.warn("skipping environ '{}' on exception '{}'".format(k, e))
1039
1040 if update_dict:
1041 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +02001042 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001043
1044 # logging cherrypy
1045 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1046 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
1047 logger_server = logging.getLogger("cherrypy.error")
1048 logger_access = logging.getLogger("cherrypy.access")
1049 logger_cherry = logging.getLogger("cherrypy")
1050 logger_nbi = logging.getLogger("nbi")
1051
tiernof5298be2018-05-16 14:43:57 +02001052 if "log.file" in engine_config["global"]:
1053 file_handler = logging.handlers.RotatingFileHandler(engine_config["global"]["log.file"],
tiernoc94c3df2018-02-09 15:38:54 +01001054 maxBytes=100e6, backupCount=9, delay=0)
1055 file_handler.setFormatter(log_formatter_simple)
1056 logger_cherry.addHandler(file_handler)
1057 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +02001058 # log always to standard output
1059 for format_, logger in {"nbi.server %(filename)s:%(lineno)s": logger_server,
1060 "nbi.access %(filename)s:%(lineno)s": logger_access,
1061 "%(name)s %(filename)s:%(lineno)s": logger_nbi
1062 }.items():
1063 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
1064 log_formatter_cherry = logging.Formatter(log_format_cherry, datefmt='%Y-%m-%dT%H:%M:%S')
1065 str_handler = logging.StreamHandler()
1066 str_handler.setFormatter(log_formatter_cherry)
1067 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +01001068
tiernof5298be2018-05-16 14:43:57 +02001069 if engine_config["global"].get("log.level"):
1070 logger_cherry.setLevel(engine_config["global"]["log.level"])
1071 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +01001072
1073 # logging other modules
1074 for k1, logname in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
1075 engine_config[k1]["logger_name"] = logname
1076 logger_module = logging.getLogger(logname)
1077 if "logfile" in engine_config[k1]:
1078 file_handler = logging.handlers.RotatingFileHandler(engine_config[k1]["logfile"],
tierno2236d202018-05-16 19:05:16 +02001079 maxBytes=100e6, backupCount=9, delay=0)
tiernoc94c3df2018-02-09 15:38:54 +01001080 file_handler.setFormatter(log_formatter_simple)
1081 logger_module.addHandler(file_handler)
1082 if "loglevel" in engine_config[k1]:
1083 logger_module.setLevel(engine_config[k1]["loglevel"])
1084 # TODO add more entries, e.g.: storage
1085 cherrypy.tree.apps['/osm'].root.engine.start(engine_config)
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001086 cherrypy.tree.apps['/osm'].root.authenticator.start(engine_config)
tiernod985a8d2018-10-19 14:12:28 +02001087 cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version)
1088 cherrypy.tree.apps['/osm'].root.authenticator.init_db(target_version=auth_database_version)
tiernobee508e2019-01-21 11:21:49 +00001089
tierno932499c2019-01-28 17:28:10 +00001090 # start subscriptions thread:
1091 subscription_thread = SubscriptionThread(config=engine_config, engine=nbi_server.engine)
1092 subscription_thread.start()
1093 # Do not capture except SubscriptionException
1094
tiernobee508e2019-01-21 11:21:49 +00001095 # load and print version. Ignore possible errors, e.g. file not found
1096 try:
1097 with open("{}/version".format(engine_config["/static"]['tools.staticdir.dir'])) as version_file:
1098 version_data = version_file.read()
1099 cherrypy.log.error("Starting OSM NBI Version: {}".format(version_data.replace("\n", " ")))
1100 except Exception:
1101 pass
tiernoc94c3df2018-02-09 15:38:54 +01001102
1103
1104def _stop_service():
1105 """
1106 Callback function called when cherrypy.engine stops
1107 TODO: Ending database connections.
1108 """
tierno932499c2019-01-28 17:28:10 +00001109 global subscription_thread
tierno65ca36d2019-02-12 19:27:52 +01001110 if subscription_thread:
1111 subscription_thread.terminate()
tierno932499c2019-01-28 17:28:10 +00001112 subscription_thread = None
tiernoc94c3df2018-02-09 15:38:54 +01001113 cherrypy.tree.apps['/osm'].root.engine.stop()
1114 cherrypy.log.error("Stopping osm_nbi")
1115
tierno2236d202018-05-16 19:05:16 +02001116
tiernof5298be2018-05-16 14:43:57 +02001117def nbi(config_file):
tierno932499c2019-01-28 17:28:10 +00001118 global nbi_server
tiernoc94c3df2018-02-09 15:38:54 +01001119 # conf = {
1120 # '/': {
1121 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1122 # 'tools.sessions.on': True,
1123 # 'tools.response_headers.on': True,
1124 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1125 # }
1126 # }
1127 # cherrypy.Server.ssl_module = 'builtin'
1128 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1129 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1130 # cherrypy.Server.thread_pool = 10
1131 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1132
1133 # cherrypy.config.update({'tools.auth_basic.on': True,
1134 # 'tools.auth_basic.realm': 'localhost',
1135 # 'tools.auth_basic.checkpassword': validate_password})
tierno932499c2019-01-28 17:28:10 +00001136 nbi_server = Server()
tiernoc94c3df2018-02-09 15:38:54 +01001137 cherrypy.engine.subscribe('start', _start_service)
1138 cherrypy.engine.subscribe('stop', _stop_service)
tierno932499c2019-01-28 17:28:10 +00001139 cherrypy.quickstart(nbi_server, '/osm', config_file)
tiernof5298be2018-05-16 14:43:57 +02001140
1141
1142def usage():
1143 print("""Usage: {} [options]
1144 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1145 -h|--help: shows this help
1146 """.format(sys.argv[0]))
tierno2236d202018-05-16 19:05:16 +02001147 # --log-socket-host HOST: send logs to this host")
1148 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +01001149
1150
1151if __name__ == '__main__':
tiernof5298be2018-05-16 14:43:57 +02001152 try:
1153 # load parameters and configuration
1154 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
1155 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1156 config_file = None
1157 for o, a in opts:
1158 if o in ("-h", "--help"):
1159 usage()
1160 sys.exit()
1161 elif o in ("-c", "--config"):
1162 config_file = a
1163 # elif o == "--log-socket-port":
1164 # log_socket_port = a
1165 # elif o == "--log-socket-host":
1166 # log_socket_host = a
1167 # elif o == "--log-file":
1168 # log_file = a
1169 else:
1170 assert False, "Unhandled option"
1171 if config_file:
1172 if not path.isfile(config_file):
1173 print("configuration file '{}' that not exist".format(config_file), file=sys.stderr)
1174 exit(1)
1175 else:
1176 for config_file in (__file__[:__file__.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
1177 if path.isfile(config_file):
1178 break
1179 else:
1180 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys.stderr)
1181 exit(1)
1182 nbi(config_file)
1183 except getopt.GetoptError as e:
1184 print(str(e), file=sys.stderr)
1185 # usage()
1186 exit(1)