blob: 4468f267a5d4808b06c06e128c5fc197ac089b39 [file] [log] [blame]
tiernoc94c3df2018-02-09 15:38:54 +01001#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3
4import cherrypy
5import time
6import json
7import yaml
8import html_out as html
9import logging
tiernof5298be2018-05-16 14:43:57 +020010import logging.handlers
11import getopt
12import sys
tiernoc94c3df2018-02-09 15:38:54 +010013from engine import Engine, EngineException
tiernoa8d63632018-05-10 13:12:32 +020014from osm_common.dbbase import DbException
15from osm_common.fsbase import FsException
16from osm_common.msgbase import MsgException
tiernoc94c3df2018-02-09 15:38:54 +010017from base64 import standard_b64decode
tiernoc94c3df2018-02-09 15:38:54 +010018from http import HTTPStatus
tiernoc94c3df2018-02-09 15:38:54 +010019from codecs import getreader
tiernof5298be2018-05-16 14:43:57 +020020from os import environ, path
tiernoc94c3df2018-02-09 15:38:54 +010021
22__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
tiernodfe09572018-04-24 10:41:10 +020023
24# TODO consider to remove and provide version using the static version file
25__version__ = "0.1.3"
tierno55945e72018-04-06 16:40:27 +020026version_date = "Apr 2018"
tierno4a946e42018-04-12 17:48:49 +020027database_version = '1.0'
tiernoc94c3df2018-02-09 15:38:54 +010028
29"""
tiernof27c79b2018-03-12 17:08:42 +010030North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
tiernoc94c3df2018-02-09 15:38:54 +010031URL: /osm GET POST PUT DELETE PATCH
tiernof27c79b2018-03-12 17:08:42 +010032 /nsd/v1 O O
tierno2236d202018-05-16 19:05:16 +020033 /ns_descriptors_content O O
34 /<nsdInfoId> O O O O
tiernoc94c3df2018-02-09 15:38:54 +010035 /ns_descriptors O5 O5
36 /<nsdInfoId> O5 O5 5
37 /nsd_content O5 O5
tiernof27c79b2018-03-12 17:08:42 +010038 /nsd O
39 /artifacts[/<artifactPath>] O
tiernoc94c3df2018-02-09 15:38:54 +010040 /pnf_descriptors 5 5
41 /<pnfdInfoId> 5 5 5
42 /pnfd_content 5 5
tiernof27c79b2018-03-12 17:08:42 +010043 /subscriptions 5 5
44 /<subscriptionId> 5 X
tiernoc94c3df2018-02-09 15:38:54 +010045
46 /vnfpkgm/v1
tierno55945e72018-04-06 16:40:27 +020047 /vnf_packages_content O O
tierno2236d202018-05-16 19:05:16 +020048 /<vnfPkgId> O O
tiernoc94c3df2018-02-09 15:38:54 +010049 /vnf_packages O5 O5
50 /<vnfPkgId> O5 O5 5
tiernoc94c3df2018-02-09 15:38:54 +010051 /package_content O5 O5
52 /upload_from_uri X
tiernof27c79b2018-03-12 17:08:42 +010053 /vnfd O5
54 /artifacts[/<artifactPath>] O5
55 /subscriptions X X
56 /<subscriptionId> X X
tiernoc94c3df2018-02-09 15:38:54 +010057
58 /nslcm/v1
tiernof27c79b2018-03-12 17:08:42 +010059 /ns_instances_content O O
tierno2236d202018-05-16 19:05:16 +020060 /<nsInstanceId> O O
tiernof27c79b2018-03-12 17:08:42 +010061 /ns_instances 5 5
tierno95692442018-05-24 18:05:28 +020062 /<nsInstanceId> O5 O5
tierno65acb4d2018-04-06 16:42:40 +020063 instantiate O5
64 terminate O5
65 action O
66 scale O5
67 heal 5
tiernoc94c3df2018-02-09 15:38:54 +010068 /ns_lcm_op_occs 5 5
69 /<nsLcmOpOccId> 5 5 5
70 TO BE COMPLETED 5 5
tierno0ffaa992018-05-09 13:21:56 +020071 /vnfrs O
72 /<vnfrId> O
tiernof27c79b2018-03-12 17:08:42 +010073 /subscriptions 5 5
74 /<subscriptionId> 5 X
75 /admin/v1
76 /tokens O O
tierno2236d202018-05-16 19:05:16 +020077 /<id> O O
tiernof27c79b2018-03-12 17:08:42 +010078 /users O O
tierno2236d202018-05-16 19:05:16 +020079 /<id> O O
tiernof27c79b2018-03-12 17:08:42 +010080 /projects O O
tierno2236d202018-05-16 19:05:16 +020081 /<id> O O
tierno09c073e2018-04-26 13:36:48 +020082 /vims_accounts (also vims for compatibility) O O
tierno2236d202018-05-16 19:05:16 +020083 /<id> O O O
tierno0f98af52018-03-19 10:28:22 +010084 /sdns O O
tierno2236d202018-05-16 19:05:16 +020085 /<id> O O O
tiernoc94c3df2018-02-09 15:38:54 +010086
tierno2236d202018-05-16 19:05:16 +020087query string:
88 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
89 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
90 item of the array, that is, pass if any item of the array pass the filter.
91 It allows both ne and neq for not equal
92 TODO: 4.3.3 Attribute selectors
93 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
tiernoc94c3df2018-02-09 15:38:54 +010094 (none) … same as “exclude_default”
95 all_fields … all attributes.
tierno2236d202018-05-16 19:05:16 +020096 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
97 conditionally mandatory, and that are not provided in <list>.
98 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
99 are not conditionally mandatory, and that are provided in <list>.
100 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
101 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
102 the particular resource
103 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
104 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
105 present specification for the particular resource, but that are not part of <list>
tiernoc94c3df2018-02-09 15:38:54 +0100106Header field name Reference Example Descriptions
107 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
108 This header field shall be present if the response is expected to have a non-empty message body.
109 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
110 This header field shall be present if the request has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200111 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
112 Details are specified in clause 4.5.3.
tiernoc94c3df2018-02-09 15:38:54 +0100113 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
114Header field name Reference Example Descriptions
115 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
116 This header field shall be present if the response has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200117 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
118 new resource has been created.
tiernoc94c3df2018-02-09 15:38:54 +0100119 This header field shall be present if the response status code is 201 or 3xx.
tierno2236d202018-05-16 19:05:16 +0200120 In the present document this header field is also used if the response status code is 202 and a new resource was
121 created.
122 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
123 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
124 token.
125 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
126 certain resources.
127 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
128 response, and the total length of the file.
tiernoc94c3df2018-02-09 15:38:54 +0100129 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
tiernoc94c3df2018-02-09 15:38:54 +0100130"""
131
132
133class NbiException(Exception):
134
135 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
136 Exception.__init__(self, message)
137 self.http_code = http_code
138
139
140class Server(object):
141 instance = 0
142 # to decode bytes to str
143 reader = getreader("utf-8")
144
145 def __init__(self):
146 self.instance += 1
147 self.engine = Engine()
tiernof27c79b2018-03-12 17:08:42 +0100148 self.valid_methods = { # contains allowed URL and methods
149 "admin": {
150 "v1": {
tierno0f98af52018-03-19 10:28:22 +0100151 "tokens": {"METHODS": ("GET", "POST", "DELETE"),
tierno2236d202018-05-16 19:05:16 +0200152 "<ID>": {"METHODS": ("GET", "DELETE")}
153 },
tierno0f98af52018-03-19 10:28:22 +0100154 "users": {"METHODS": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200155 "<ID>": {"METHODS": ("GET", "POST", "DELETE")}
156 },
tierno0f98af52018-03-19 10:28:22 +0100157 "projects": {"METHODS": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200158 "<ID>": {"METHODS": ("GET", "DELETE")}
159 },
tierno0f98af52018-03-19 10:28:22 +0100160 "vims": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200161 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200162 },
tierno09c073e2018-04-26 13:36:48 +0200163 "vim_accounts": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200164 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200165 },
tierno0f98af52018-03-19 10:28:22 +0100166 "sdns": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200167 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200168 },
tiernof27c79b2018-03-12 17:08:42 +0100169 }
170 },
171 "nsd": {
172 "v1": {
tierno2236d202018-05-16 19:05:16 +0200173 "ns_descriptors_content": {"METHODS": ("GET", "POST"),
174 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
175 },
176 "ns_descriptors": {"METHODS": ("GET", "POST"),
177 "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH",
178 "nsd_content": {"METHODS": ("GET", "PUT")},
179 "nsd": {"METHODS": "GET"}, # descriptor inside package
180 "artifacts": {"*": {"METHODS": "GET"}}
181 }
182 },
tiernof27c79b2018-03-12 17:08:42 +0100183 "pnf_descriptors": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200184 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
185 "pnfd_content": {"TODO": ("GET", "PUT")}
186 }
187 },
tiernof27c79b2018-03-12 17:08:42 +0100188 "subscriptions": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200189 "<ID>": {"TODO": ("GET", "DELETE")}
190 },
tiernof27c79b2018-03-12 17:08:42 +0100191 }
192 },
193 "vnfpkgm": {
194 "v1": {
tierno2236d202018-05-16 19:05:16 +0200195 "vnf_packages_content": {"METHODS": ("GET", "POST"),
196 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
197 },
198 "vnf_packages": {"METHODS": ("GET", "POST"),
199 "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH", # GET: vnfPkgInfo
200 "package_content": {"METHODS": ("GET", "PUT"), # package
201 "upload_from_uri": {"TODO": "POST"}
202 },
203 "vnfd": {"METHODS": "GET"}, # descriptor inside package
204 "artifacts": {"*": {"METHODS": "GET"}}
205 }
206 },
tiernof27c79b2018-03-12 17:08:42 +0100207 "subscriptions": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200208 "<ID>": {"TODO": ("GET", "DELETE")}
209 },
tiernof27c79b2018-03-12 17:08:42 +0100210 }
211 },
212 "nslcm": {
213 "v1": {
214 "ns_instances_content": {"METHODS": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200215 "<ID>": {"METHODS": ("GET", "DELETE")}
216 },
tierno65acb4d2018-04-06 16:42:40 +0200217 "ns_instances": {"METHODS": ("GET", "POST"),
tierno95692442018-05-24 18:05:28 +0200218 "<ID>": {"METHODS": ("GET", "DELETE"),
tierno2236d202018-05-16 19:05:16 +0200219 "scale": {"TODO": "POST"},
220 "terminate": {"METHODS": "POST"},
221 "instantiate": {"METHODS": "POST"},
222 "action": {"METHODS": "POST"},
223 }
224 },
tierno65acb4d2018-04-06 16:42:40 +0200225 "ns_lcm_op_occs": {"METHODS": "GET",
tierno2236d202018-05-16 19:05:16 +0200226 "<ID>": {"METHODS": "GET"},
227 },
tierno0ffaa992018-05-09 13:21:56 +0200228 "vnfrs": {"METHODS": ("GET"),
tierno2236d202018-05-16 19:05:16 +0200229 "<ID>": {"METHODS": ("GET")}
230 },
tiernof27c79b2018-03-12 17:08:42 +0100231 }
232 },
233 }
tiernoc94c3df2018-02-09 15:38:54 +0100234
235 def _authorization(self):
236 token = None
237 user_passwd64 = None
238 try:
239 # 1. Get token Authorization bearer
240 auth = cherrypy.request.headers.get("Authorization")
241 if auth:
242 auth_list = auth.split(" ")
243 if auth_list[0].lower() == "bearer":
244 token = auth_list[-1]
245 elif auth_list[0].lower() == "basic":
246 user_passwd64 = auth_list[-1]
247 if not token:
248 if cherrypy.session.get("Authorization"):
249 # 2. Try using session before request a new token. If not, basic authentication will generate
250 token = cherrypy.session.get("Authorization")
251 if token == "logout":
252 token = None # force Unauthorized response to insert user pasword again
253 elif user_passwd64 and cherrypy.request.config.get("auth.allow_basic_authentication"):
254 # 3. Get new token from user password
255 user = None
256 passwd = None
257 try:
258 user_passwd = standard_b64decode(user_passwd64).decode()
259 user, _, passwd = user_passwd.partition(":")
tiernoe1281182018-05-22 12:24:36 +0200260 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100261 pass
262 outdata = self.engine.new_token(None, {"username": user, "password": passwd})
263 token = outdata["id"]
264 cherrypy.session['Authorization'] = token
265 # 4. Get token from cookie
266 # if not token:
267 # auth_cookie = cherrypy.request.cookie.get("Authorization")
268 # if auth_cookie:
269 # token = auth_cookie.value
270 return self.engine.authorize(token)
271 except EngineException as e:
272 if cherrypy.session.get('Authorization'):
273 del cherrypy.session['Authorization']
274 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="{}"'.format(e)
275 raise
276
277 def _format_in(self, kwargs):
278 try:
279 indata = None
280 if cherrypy.request.body.length:
281 error_text = "Invalid input format "
282
283 if "Content-Type" in cherrypy.request.headers:
284 if "application/json" in cherrypy.request.headers["Content-Type"]:
285 error_text = "Invalid json format "
286 indata = json.load(self.reader(cherrypy.request.body))
287 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
288 error_text = "Invalid yaml format "
289 indata = yaml.load(cherrypy.request.body)
290 elif "application/binary" in cherrypy.request.headers["Content-Type"] or \
291 "application/gzip" in cherrypy.request.headers["Content-Type"] or \
tiernof27c79b2018-03-12 17:08:42 +0100292 "application/zip" in cherrypy.request.headers["Content-Type"] or \
293 "text/plain" in cherrypy.request.headers["Content-Type"]:
294 indata = cherrypy.request.body # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100295 elif "multipart/form-data" in cherrypy.request.headers["Content-Type"]:
296 if "descriptor_file" in kwargs:
297 filecontent = kwargs.pop("descriptor_file")
298 if not filecontent.file:
299 raise NbiException("empty file or content", HTTPStatus.BAD_REQUEST)
tiernof27c79b2018-03-12 17:08:42 +0100300 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100301 if filecontent.content_type.value:
302 cherrypy.request.headers["Content-Type"] = filecontent.content_type.value
303 else:
304 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
305 # "Only 'Content-Type' of type 'application/json' or
306 # 'application/yaml' for input format are available")
307 error_text = "Invalid yaml format "
308 indata = yaml.load(cherrypy.request.body)
309 else:
310 error_text = "Invalid yaml format "
311 indata = yaml.load(cherrypy.request.body)
312 if not indata:
313 indata = {}
314
tiernoc94c3df2018-02-09 15:38:54 +0100315 format_yaml = False
316 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
317 format_yaml = True
318
319 for k, v in kwargs.items():
320 if isinstance(v, str):
321 if v == "":
322 kwargs[k] = None
323 elif format_yaml:
324 try:
325 kwargs[k] = yaml.load(v)
tiernoe1281182018-05-22 12:24:36 +0200326 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100327 pass
328 elif k.endswith(".gt") or k.endswith(".lt") or k.endswith(".gte") or k.endswith(".lte"):
329 try:
330 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200331 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100332 try:
333 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200334 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100335 pass
336 elif v.find(",") > 0:
337 kwargs[k] = v.split(",")
338 elif isinstance(v, (list, tuple)):
339 for index in range(0, len(v)):
340 if v[index] == "":
341 v[index] = None
342 elif format_yaml:
343 try:
344 v[index] = yaml.load(v[index])
tiernoe1281182018-05-22 12:24:36 +0200345 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100346 pass
347
tiernof27c79b2018-03-12 17:08:42 +0100348 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100349 except (ValueError, yaml.YAMLError) as exc:
350 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
351 except KeyError as exc:
352 raise NbiException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
tiernob92094f2018-05-11 13:44:22 +0200353 except Exception as exc:
354 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100355
356 @staticmethod
tiernof27c79b2018-03-12 17:08:42 +0100357 def _format_out(data, session=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100358 """
359 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100360 :param data: response to be sent. Can be a dict, text or file
tiernoc94c3df2018-02-09 15:38:54 +0100361 :param session:
tiernof27c79b2018-03-12 17:08:42 +0100362 :param _format: The format to be set as Content-Type ir data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100363 :return: None
364 """
tierno0f98af52018-03-19 10:28:22 +0100365 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100366 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100367 if accept and "text/html" in accept:
368 return html.format(data, cherrypy.request, cherrypy.response, session)
tierno09c073e2018-04-26 13:36:48 +0200369 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100370 return
371 elif hasattr(data, "read"): # file object
372 if _format:
373 cherrypy.response.headers["Content-Type"] = _format
374 elif "b" in data.mode: # binariy asssumig zip
375 cherrypy.response.headers["Content-Type"] = 'application/zip'
376 else:
377 cherrypy.response.headers["Content-Type"] = 'text/plain'
378 # TODO check that cherrypy close file. If not implement pending things to close per thread next
379 return data
tierno0f98af52018-03-19 10:28:22 +0100380 if accept:
tiernoc94c3df2018-02-09 15:38:54 +0100381 if "application/json" in accept:
382 cherrypy.response.headers["Content-Type"] = 'application/json; charset=utf-8'
383 a = json.dumps(data, indent=4) + "\n"
384 return a.encode("utf8")
385 elif "text/html" in accept:
386 return html.format(data, cherrypy.request, cherrypy.response, session)
387
tiernof27c79b2018-03-12 17:08:42 +0100388 elif "application/yaml" in accept or "*/*" in accept or "text/plain" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100389 pass
390 else:
391 raise cherrypy.HTTPError(HTTPStatus.NOT_ACCEPTABLE.value,
392 "Only 'Accept' of type 'application/json' or 'application/yaml' "
393 "for output format are available")
394 cherrypy.response.headers["Content-Type"] = 'application/yaml'
395 return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False,
396 encoding='utf-8', allow_unicode=True) # , canonical=True, default_style='"'
397
398 @cherrypy.expose
399 def index(self, *args, **kwargs):
400 session = None
401 try:
402 if cherrypy.request.method == "GET":
403 session = self._authorization()
404 outdata = "Index page"
405 else:
406 raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value,
tierno2236d202018-05-16 19:05:16 +0200407 "Method {} not allowed for tokens".format(cherrypy.request.method))
tiernoc94c3df2018-02-09 15:38:54 +0100408
409 return self._format_out(outdata, session)
410
411 except EngineException as e:
412 cherrypy.log("index Exception {}".format(e))
413 cherrypy.response.status = e.http_code.value
414 return self._format_out("Welcome to OSM!", session)
415
416 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200417 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200418 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200419 global __version__, version_date
420 try:
421 if cherrypy.request.method != "GET":
422 raise NbiException("Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED)
423 elif args or kwargs:
424 raise NbiException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED)
425 return __version__ + " " + version_date
426 except NbiException as e:
427 cherrypy.response.status = e.http_code.value
428 problem_details = {
429 "code": e.http_code.name,
430 "status": e.http_code.value,
431 "detail": str(e),
432 }
433 return self._format_out(problem_details, None)
434
435 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100436 def token(self, method, token_id=None, kwargs=None):
tiernoc94c3df2018-02-09 15:38:54 +0100437 session = None
438 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +0100439 indata = self._format_in(kwargs)
440 if not isinstance(indata, dict):
441 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100442 try:
tiernoc94c3df2018-02-09 15:38:54 +0100443 if method == "GET":
444 session = self._authorization()
tiernof27c79b2018-03-12 17:08:42 +0100445 if token_id:
446 outdata = self.engine.get_token(session, token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100447 else:
448 outdata = self.engine.get_token_list(session)
449 elif method == "POST":
450 try:
451 session = self._authorization()
tiernoe1281182018-05-22 12:24:36 +0200452 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100453 session = None
454 if kwargs:
455 indata.update(kwargs)
456 outdata = self.engine.new_token(session, indata, cherrypy.request.remote)
457 session = outdata
458 cherrypy.session['Authorization'] = outdata["_id"]
tierno0f98af52018-03-19 10:28:22 +0100459 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
tiernoc94c3df2018-02-09 15:38:54 +0100460 # cherrypy.response.cookie["Authorization"] = outdata["id"]
461 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
462 elif method == "DELETE":
tiernof27c79b2018-03-12 17:08:42 +0100463 if not token_id and "id" in kwargs:
tiernoc94c3df2018-02-09 15:38:54 +0100464 token_id = kwargs["id"]
tiernof27c79b2018-03-12 17:08:42 +0100465 elif not token_id:
tiernoc94c3df2018-02-09 15:38:54 +0100466 session = self._authorization()
467 token_id = session["_id"]
468 outdata = self.engine.del_token(token_id)
469 session = None
470 cherrypy.session['Authorization'] = "logout"
471 # cherrypy.response.cookie["Authorization"] = token_id
472 # cherrypy.response.cookie["Authorization"]['expires'] = 0
473 else:
474 raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
475 return self._format_out(outdata, session)
476 except (NbiException, EngineException, DbException) as e:
477 cherrypy.log("tokens Exception {}".format(e))
478 cherrypy.response.status = e.http_code.value
479 problem_details = {
480 "code": e.http_code.name,
481 "status": e.http_code.value,
482 "detail": str(e),
483 }
484 return self._format_out(problem_details, session)
485
486 @cherrypy.expose
487 def test(self, *args, **kwargs):
488 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +0100489 if args and args[0] == "help":
490 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nprune\nlogin\nlogin2\n"\
tiernoe1281182018-05-22 12:24:36 +0200491 "sleep/<time>\nmessage/topic\n</pre></html>"
tiernof27c79b2018-03-12 17:08:42 +0100492
493 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +0100494 try:
495 # self.engine.load_dbase(cherrypy.request.app.config)
496 self.engine.create_admin()
497 return "Done. User 'admin', password 'admin' created"
498 except Exception:
499 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
500 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +0100501 elif args and args[0] == "file":
502 return cherrypy.lib.static.serve_file(cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1],
503 "text/plain", "attachment")
504 elif args and args[0] == "file2":
505 f_path = cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1]
506 f = open(f_path, "r")
507 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100508 return f
tierno55945e72018-04-06 16:40:27 +0200509
tiernof27c79b2018-03-12 17:08:42 +0100510 elif len(args) == 2 and args[0] == "db-clear":
511 return self.engine.del_item_list({"project_id": "admin"}, args[1], {})
tiernoc94c3df2018-02-09 15:38:54 +0100512 elif args and args[0] == "prune":
513 return self.engine.prune()
514 elif args and args[0] == "login":
515 if not cherrypy.request.headers.get("Authorization"):
516 cherrypy.response.headers["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
517 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
518 elif args and args[0] == "login2":
519 if not cherrypy.request.headers.get("Authorization"):
520 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
521 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
522 elif args and args[0] == "sleep":
523 sleep_time = 5
524 try:
525 sleep_time = int(args[1])
526 except Exception:
527 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
528 return self._format_out("Database already initialized")
529 thread_info = cherrypy.thread_data
530 print(thread_info)
531 time.sleep(sleep_time)
532 # thread_info
533 elif len(args) >= 2 and args[0] == "message":
534 topic = args[1]
tierno55945e72018-04-06 16:40:27 +0200535 return_text = "<html><pre>{} ->\n".format(topic)
tiernoc94c3df2018-02-09 15:38:54 +0100536 try:
tierno55945e72018-04-06 16:40:27 +0200537 if cherrypy.request.method == 'POST':
538 to_send = yaml.load(cherrypy.request.body)
539 for k, v in to_send.items():
540 self.engine.msg.write(topic, k, v)
541 return_text += " {}: {}\n".format(k, v)
542 elif cherrypy.request.method == 'GET':
543 for k, v in kwargs.items():
544 self.engine.msg.write(topic, k, yaml.load(v))
545 return_text += " {}: {}\n".format(k, yaml.load(v))
tiernoc94c3df2018-02-09 15:38:54 +0100546 except Exception as e:
tierno55945e72018-04-06 16:40:27 +0200547 return_text += "Error: " + str(e)
548 return_text += "</pre></html>\n"
549 return return_text
tiernoc94c3df2018-02-09 15:38:54 +0100550
551 return_text = (
552 "<html><pre>\nheaders:\n args: {}\n".format(args) +
553 " kwargs: {}\n".format(kwargs) +
554 " headers: {}\n".format(cherrypy.request.headers) +
555 " path_info: {}\n".format(cherrypy.request.path_info) +
556 " query_string: {}\n".format(cherrypy.request.query_string) +
557 " session: {}\n".format(cherrypy.session) +
558 " cookie: {}\n".format(cherrypy.request.cookie) +
559 " method: {}\n".format(cherrypy.request.method) +
560 " session: {}\n".format(cherrypy.session.get('fieldname')) +
561 " body:\n")
562 return_text += " length: {}\n".format(cherrypy.request.body.length)
563 if cherrypy.request.body.length:
564 return_text += " content: {}\n".format(
565 str(cherrypy.request.body.read(int(cherrypy.request.headers.get('Content-Length', 0)))))
566 if thread_info:
567 return_text += "thread: {}\n".format(thread_info)
568 return_text += "</pre></html>"
569 return return_text
570
tiernof27c79b2018-03-12 17:08:42 +0100571 def _check_valid_url_method(self, method, *args):
572 if len(args) < 3:
573 raise NbiException("URL must contain at least 'topic/version/item'", HTTPStatus.METHOD_NOT_ALLOWED)
574
575 reference = self.valid_methods
576 for arg in args:
577 if arg is None:
578 break
579 if not isinstance(reference, dict):
580 raise NbiException("URL contains unexpected extra items '{}'".format(arg),
581 HTTPStatus.METHOD_NOT_ALLOWED)
582
583 if arg in reference:
584 reference = reference[arg]
585 elif "<ID>" in reference:
586 reference = reference["<ID>"]
587 elif "*" in reference:
588 reference = reference["*"]
589 break
590 else:
591 raise NbiException("Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED)
592 if "TODO" in reference and method in reference["TODO"]:
593 raise NbiException("Method {} not supported yet for this URL".format(method), HTTPStatus.NOT_IMPLEMENTED)
tierno2236d202018-05-16 19:05:16 +0200594 elif "METHODS" in reference and method not in reference["METHODS"]:
tiernof27c79b2018-03-12 17:08:42 +0100595 raise NbiException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
596 return
597
598 @staticmethod
599 def _set_location_header(topic, version, item, id):
600 """
601 Insert response header Location with the URL of created item base on URL params
602 :param topic:
603 :param version:
604 :param item:
605 :param id:
606 :return: None
607 """
608 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
609 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(topic, version, item, id)
610 return
611
tiernoc94c3df2018-02-09 15:38:54 +0100612 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100613 def default(self, topic=None, version=None, item=None, _id=None, item2=None, *args, **kwargs):
tiernoc94c3df2018-02-09 15:38:54 +0100614 session = None
tiernof27c79b2018-03-12 17:08:42 +0100615 outdata = None
616 _format = None
tierno0f98af52018-03-19 10:28:22 +0100617 method = "DONE"
618 engine_item = None
tiernodb9dc582018-06-20 17:27:29 +0200619 rollback = []
620 session = None
tiernoc94c3df2018-02-09 15:38:54 +0100621 try:
tiernof27c79b2018-03-12 17:08:42 +0100622 if not topic or not version or not item:
623 raise NbiException("URL must contain at least 'topic/version/item'", HTTPStatus.METHOD_NOT_ALLOWED)
624 if topic not in ("admin", "vnfpkgm", "nsd", "nslcm"):
625 raise NbiException("URL topic '{}' not supported".format(topic), HTTPStatus.METHOD_NOT_ALLOWED)
tiernoc94c3df2018-02-09 15:38:54 +0100626 if version != 'v1':
627 raise NbiException("URL version '{}' not supported".format(version), HTTPStatus.METHOD_NOT_ALLOWED)
628
tiernof27c79b2018-03-12 17:08:42 +0100629 if kwargs and "METHOD" in kwargs and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
630 method = kwargs.pop("METHOD")
631 else:
632 method = cherrypy.request.method
tiernob92094f2018-05-11 13:44:22 +0200633 if kwargs and "FORCE" in kwargs:
634 force = kwargs.pop("FORCE")
635 else:
636 force = False
tiernof27c79b2018-03-12 17:08:42 +0100637
638 self._check_valid_url_method(method, topic, version, item, _id, item2, *args)
639
640 if topic == "admin" and item == "tokens":
641 return self.token(method, _id, kwargs)
642
tiernoc94c3df2018-02-09 15:38:54 +0100643 # self.engine.load_dbase(cherrypy.request.app.config)
644 session = self._authorization()
tiernof27c79b2018-03-12 17:08:42 +0100645 indata = self._format_in(kwargs)
646 engine_item = item
647 if item == "subscriptions":
648 engine_item = topic + "_" + item
649 if item2:
650 engine_item = item2
tiernoc94c3df2018-02-09 15:38:54 +0100651
tiernof27c79b2018-03-12 17:08:42 +0100652 if topic == "nsd":
653 engine_item = "nsds"
654 elif topic == "vnfpkgm":
655 engine_item = "vnfds"
656 elif topic == "nslcm":
657 engine_item = "nsrs"
tierno65acb4d2018-04-06 16:42:40 +0200658 if item == "ns_lcm_op_occs":
659 engine_item = "nslcmops"
tierno0ffaa992018-05-09 13:21:56 +0200660 if item == "vnfrs":
661 engine_item = "vnfrs"
tierno09c073e2018-04-26 13:36:48 +0200662 if engine_item == "vims": # TODO this is for backward compatibility, it will remove in the future
663 engine_item = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +0100664
665 if method == "GET":
tiernof27c79b2018-03-12 17:08:42 +0100666 if item2 in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd"):
667 if item2 in ("vnfd", "nsd"):
668 path = "$DESCRIPTOR"
669 elif args:
670 path = args
671 elif item2 == "artifacts":
672 path = ()
673 else:
674 path = None
675 file, _format = self.engine.get_file(session, engine_item, _id, path,
tierno2236d202018-05-16 19:05:16 +0200676 cherrypy.request.headers.get("Accept"))
tiernof27c79b2018-03-12 17:08:42 +0100677 outdata = file
678 elif not _id:
679 outdata = self.engine.get_item_list(session, engine_item, kwargs)
tiernoc94c3df2018-02-09 15:38:54 +0100680 else:
tiernof27c79b2018-03-12 17:08:42 +0100681 outdata = self.engine.get_item(session, engine_item, _id)
682 elif method == "POST":
683 if item in ("ns_descriptors_content", "vnf_packages_content"):
684 _id = cherrypy.request.headers.get("Transaction-Id")
685 if not _id:
tiernodb9dc582018-06-20 17:27:29 +0200686 _id = self.engine.new_item(rollback, session, engine_item, {}, None, cherrypy.request.headers,
tiernob92094f2018-05-11 13:44:22 +0200687 force=force)
tierno2236d202018-05-16 19:05:16 +0200688 completed = self.engine.upload_content(session, engine_item, _id, indata, kwargs,
689 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +0100690 if completed:
691 self._set_location_header(topic, version, item, _id)
692 else:
693 cherrypy.response.headers["Transaction-Id"] = _id
694 outdata = {"id": _id}
tierno65acb4d2018-04-06 16:42:40 +0200695 elif item == "ns_instances_content":
tiernodb9dc582018-06-20 17:27:29 +0200696 _id = self.engine.new_item(rollback, session, engine_item, indata, kwargs, force=force)
697 self.engine.ns_operation(rollback, session, _id, "instantiate", {}, None)
tiernof27c79b2018-03-12 17:08:42 +0100698 self._set_location_header(topic, version, item, _id)
tiernof27c79b2018-03-12 17:08:42 +0100699 outdata = {"id": _id}
tierno65acb4d2018-04-06 16:42:40 +0200700 elif item == "ns_instances" and item2:
tiernodb9dc582018-06-20 17:27:29 +0200701 _id = self.engine.ns_operation(rollback, session, _id, item2, indata, kwargs)
tierno65acb4d2018-04-06 16:42:40 +0200702 self._set_location_header(topic, version, "ns_lcm_op_occs", _id)
703 outdata = {"id": _id}
704 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +0100705 else:
tiernodb9dc582018-06-20 17:27:29 +0200706 _id = self.engine.new_item(rollback, session, engine_item, indata, kwargs, cherrypy.request.headers,
tiernob92094f2018-05-11 13:44:22 +0200707 force=force)
tiernof27c79b2018-03-12 17:08:42 +0100708 self._set_location_header(topic, version, item, _id)
709 outdata = {"id": _id}
tierno65acb4d2018-04-06 16:42:40 +0200710 # TODO form NsdInfo when item in ("ns_descriptors", "vnf_packages")
tiernof27c79b2018-03-12 17:08:42 +0100711 cherrypy.response.status = HTTPStatus.CREATED.value
tierno09c073e2018-04-26 13:36:48 +0200712
tiernoc94c3df2018-02-09 15:38:54 +0100713 elif method == "DELETE":
714 if not _id:
tiernof27c79b2018-03-12 17:08:42 +0100715 outdata = self.engine.del_item_list(session, engine_item, kwargs)
tierno09c073e2018-04-26 13:36:48 +0200716 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +0100717 else: # len(args) > 1
tiernob62b7ac2018-05-28 16:50:20 +0200718 if item == "ns_instances_content" and not force:
tiernodb9dc582018-06-20 17:27:29 +0200719 opp_id = self.engine.ns_operation(rollback, session, _id, "terminate", {"autoremove": True},
720 None)
tierno3ace63c2018-05-03 17:51:43 +0200721 outdata = {"_id": opp_id}
tierno09c073e2018-04-26 13:36:48 +0200722 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tierno65acb4d2018-04-06 16:42:40 +0200723 else:
tierno65acb4d2018-04-06 16:42:40 +0200724 self.engine.del_item(session, engine_item, _id, force)
tierno09c073e2018-04-26 13:36:48 +0200725 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
726 if engine_item in ("vim_accounts", "sdns"):
727 cherrypy.response.status = HTTPStatus.ACCEPTED.value
728
tierno7ae10112018-05-18 14:36:02 +0200729 elif method in ("PUT", "PATCH"):
tiernof27c79b2018-03-12 17:08:42 +0100730 if not indata and not kwargs:
tiernoc94c3df2018-02-09 15:38:54 +0100731 raise NbiException("Nothing to update. Provide payload and/or query string",
732 HTTPStatus.BAD_REQUEST)
tierno7ae10112018-05-18 14:36:02 +0200733 if item2 in ("nsd_content", "package_content") and method == "PUT":
tierno2236d202018-05-16 19:05:16 +0200734 completed = self.engine.upload_content(session, engine_item, _id, indata, kwargs,
735 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +0100736 if not completed:
737 cherrypy.response.headers["Transaction-Id"] = id
tierno09c073e2018-04-26 13:36:48 +0200738 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100739 outdata = None
740 else:
tiernob92094f2018-05-11 13:44:22 +0200741 outdata = {"id": self.engine.edit_item(session, engine_item, _id, indata, kwargs, force=force)}
tiernoc94c3df2018-02-09 15:38:54 +0100742 else:
743 raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tiernof27c79b2018-03-12 17:08:42 +0100744 return self._format_out(outdata, session, _format)
tierno0f98af52018-03-19 10:28:22 +0100745 except (NbiException, EngineException, DbException, FsException, MsgException) as e:
tiernoc94c3df2018-02-09 15:38:54 +0100746 cherrypy.log("Exception {}".format(e))
747 cherrypy.response.status = e.http_code.value
tierno3ace63c2018-05-03 17:51:43 +0200748 if hasattr(outdata, "close"): # is an open file
749 outdata.close()
tiernodb9dc582018-06-20 17:27:29 +0200750 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +0200751 try:
tiernodb9dc582018-06-20 17:27:29 +0200752 self.engine.del_item(**rollback_item, session=session, force=True)
tierno3ace63c2018-05-03 17:51:43 +0200753 except Exception as e2:
tiernodb9dc582018-06-20 17:27:29 +0200754 cherrypy.log("Rollback Exception {}: {}".format(rollback_item, e2))
tierno0f98af52018-03-19 10:28:22 +0100755 error_text = str(e)
756 if isinstance(e, MsgException):
757 error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
758 engine_item[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +0100759 problem_details = {
760 "code": e.http_code.name,
761 "status": e.http_code.value,
762 "detail": str(e),
763 }
764 return self._format_out(problem_details, session)
765 # raise cherrypy.HTTPError(e.http_code.value, str(e))
766
767
768# def validate_password(realm, username, password):
769# cherrypy.log("realm "+ str(realm))
770# if username == "admin" and password == "admin":
771# return True
772# return False
773
774
775def _start_service():
776 """
777 Callback function called when cherrypy.engine starts
778 Override configuration with env variables
779 Set database, storage, message configuration
780 Init database with admin/admin user password
781 """
782 cherrypy.log.error("Starting osm_nbi")
783 # update general cherrypy configuration
784 update_dict = {}
785
786 engine_config = cherrypy.tree.apps['/osm'].config
787 for k, v in environ.items():
788 if not k.startswith("OSMNBI_"):
789 continue
tiernoe1281182018-05-22 12:24:36 +0200790 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +0100791 if not k2:
792 continue
793 try:
794 # update static configuration
795 if k == 'OSMNBI_STATIC_DIR':
796 engine_config["/static"]['tools.staticdir.dir'] = v
797 engine_config["/static"]['tools.staticdir.on'] = True
798 elif k == 'OSMNBI_SOCKET_PORT' or k == 'OSMNBI_SERVER_PORT':
799 update_dict['server.socket_port'] = int(v)
800 elif k == 'OSMNBI_SOCKET_HOST' or k == 'OSMNBI_SERVER_HOST':
801 update_dict['server.socket_host'] = v
tiernof5298be2018-05-16 14:43:57 +0200802 elif k1 in ("server", "test", "auth", "log"):
803 update_dict[k1 + '.' + k2] = v
tiernoc94c3df2018-02-09 15:38:54 +0100804 elif k1 in ("message", "database", "storage"):
tiernof5298be2018-05-16 14:43:57 +0200805 # k2 = k2.replace('_', '.')
tiernoc94c3df2018-02-09 15:38:54 +0100806 if k2 == "port":
807 engine_config[k1][k2] = int(v)
808 else:
809 engine_config[k1][k2] = v
810 except ValueError as e:
811 cherrypy.log.error("Ignoring environ '{}': " + str(e))
812 except Exception as e:
813 cherrypy.log.warn("skipping environ '{}' on exception '{}'".format(k, e))
814
815 if update_dict:
816 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +0200817 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +0100818
819 # logging cherrypy
820 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
821 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
822 logger_server = logging.getLogger("cherrypy.error")
823 logger_access = logging.getLogger("cherrypy.access")
824 logger_cherry = logging.getLogger("cherrypy")
825 logger_nbi = logging.getLogger("nbi")
826
tiernof5298be2018-05-16 14:43:57 +0200827 if "log.file" in engine_config["global"]:
828 file_handler = logging.handlers.RotatingFileHandler(engine_config["global"]["log.file"],
tiernoc94c3df2018-02-09 15:38:54 +0100829 maxBytes=100e6, backupCount=9, delay=0)
830 file_handler.setFormatter(log_formatter_simple)
831 logger_cherry.addHandler(file_handler)
832 logger_nbi.addHandler(file_handler)
833 else:
834 for format_, logger in {"nbi.server": logger_server,
835 "nbi.access": logger_access,
836 "%(name)s %(filename)s:%(lineno)s": logger_nbi
837 }.items():
838 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
839 log_formatter_cherry = logging.Formatter(log_format_cherry, datefmt='%Y-%m-%dT%H:%M:%S')
840 str_handler = logging.StreamHandler()
841 str_handler.setFormatter(log_formatter_cherry)
842 logger.addHandler(str_handler)
843
tiernof5298be2018-05-16 14:43:57 +0200844 if engine_config["global"].get("log.level"):
845 logger_cherry.setLevel(engine_config["global"]["log.level"])
846 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +0100847
848 # logging other modules
849 for k1, logname in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
850 engine_config[k1]["logger_name"] = logname
851 logger_module = logging.getLogger(logname)
852 if "logfile" in engine_config[k1]:
853 file_handler = logging.handlers.RotatingFileHandler(engine_config[k1]["logfile"],
tierno2236d202018-05-16 19:05:16 +0200854 maxBytes=100e6, backupCount=9, delay=0)
tiernoc94c3df2018-02-09 15:38:54 +0100855 file_handler.setFormatter(log_formatter_simple)
856 logger_module.addHandler(file_handler)
857 if "loglevel" in engine_config[k1]:
858 logger_module.setLevel(engine_config[k1]["loglevel"])
859 # TODO add more entries, e.g.: storage
860 cherrypy.tree.apps['/osm'].root.engine.start(engine_config)
861 try:
tierno4a946e42018-04-12 17:48:49 +0200862 cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version)
tiernoc94c3df2018-02-09 15:38:54 +0100863 except EngineException:
864 pass
865 # getenv('OSMOPENMANO_TENANT', None)
866
867
868def _stop_service():
869 """
870 Callback function called when cherrypy.engine stops
871 TODO: Ending database connections.
872 """
873 cherrypy.tree.apps['/osm'].root.engine.stop()
874 cherrypy.log.error("Stopping osm_nbi")
875
tierno2236d202018-05-16 19:05:16 +0200876
tiernof5298be2018-05-16 14:43:57 +0200877def nbi(config_file):
tiernoc94c3df2018-02-09 15:38:54 +0100878 # conf = {
879 # '/': {
880 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
881 # 'tools.sessions.on': True,
882 # 'tools.response_headers.on': True,
883 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
884 # }
885 # }
886 # cherrypy.Server.ssl_module = 'builtin'
887 # cherrypy.Server.ssl_certificate = "http/cert.pem"
888 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
889 # cherrypy.Server.thread_pool = 10
890 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
891
892 # cherrypy.config.update({'tools.auth_basic.on': True,
893 # 'tools.auth_basic.realm': 'localhost',
894 # 'tools.auth_basic.checkpassword': validate_password})
895 cherrypy.engine.subscribe('start', _start_service)
896 cherrypy.engine.subscribe('stop', _stop_service)
tiernof5298be2018-05-16 14:43:57 +0200897 cherrypy.quickstart(Server(), '/osm', config_file)
898
899
900def usage():
901 print("""Usage: {} [options]
902 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
903 -h|--help: shows this help
904 """.format(sys.argv[0]))
tierno2236d202018-05-16 19:05:16 +0200905 # --log-socket-host HOST: send logs to this host")
906 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +0100907
908
909if __name__ == '__main__':
tiernof5298be2018-05-16 14:43:57 +0200910 try:
911 # load parameters and configuration
912 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
913 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
914 config_file = None
915 for o, a in opts:
916 if o in ("-h", "--help"):
917 usage()
918 sys.exit()
919 elif o in ("-c", "--config"):
920 config_file = a
921 # elif o == "--log-socket-port":
922 # log_socket_port = a
923 # elif o == "--log-socket-host":
924 # log_socket_host = a
925 # elif o == "--log-file":
926 # log_file = a
927 else:
928 assert False, "Unhandled option"
929 if config_file:
930 if not path.isfile(config_file):
931 print("configuration file '{}' that not exist".format(config_file), file=sys.stderr)
932 exit(1)
933 else:
934 for config_file in (__file__[:__file__.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
935 if path.isfile(config_file):
936 break
937 else:
938 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys.stderr)
939 exit(1)
940 nbi(config_file)
941 except getopt.GetoptError as e:
942 print(str(e), file=sys.stderr)
943 # usage()
944 exit(1)