blob: 796558deef659a31bf7190f4961d157c8cf60f0d [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
tiernof759d822018-06-11 18:54:54 +020071 /vnf_instances (also vnfrs for compatibility) O
72 /<vnfInstanceId> 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"),
tiernof759d822018-06-11 18:54:54 +0200219 "scale": {"METHODS": "POST"},
tierno2236d202018-05-16 19:05:16 +0200220 "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 },
tiernof759d822018-06-11 18:54:54 +0200231 "vnf_instances": {"METHODS": ("GET"),
232 "<ID>": {"METHODS": ("GET")}
233 },
tiernof27c79b2018-03-12 17:08:42 +0100234 }
235 },
236 }
tiernoc94c3df2018-02-09 15:38:54 +0100237
238 def _authorization(self):
239 token = None
240 user_passwd64 = None
241 try:
242 # 1. Get token Authorization bearer
243 auth = cherrypy.request.headers.get("Authorization")
244 if auth:
245 auth_list = auth.split(" ")
246 if auth_list[0].lower() == "bearer":
247 token = auth_list[-1]
248 elif auth_list[0].lower() == "basic":
249 user_passwd64 = auth_list[-1]
250 if not token:
251 if cherrypy.session.get("Authorization"):
252 # 2. Try using session before request a new token. If not, basic authentication will generate
253 token = cherrypy.session.get("Authorization")
254 if token == "logout":
255 token = None # force Unauthorized response to insert user pasword again
256 elif user_passwd64 and cherrypy.request.config.get("auth.allow_basic_authentication"):
257 # 3. Get new token from user password
258 user = None
259 passwd = None
260 try:
261 user_passwd = standard_b64decode(user_passwd64).decode()
262 user, _, passwd = user_passwd.partition(":")
tiernoe1281182018-05-22 12:24:36 +0200263 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100264 pass
265 outdata = self.engine.new_token(None, {"username": user, "password": passwd})
266 token = outdata["id"]
267 cherrypy.session['Authorization'] = token
268 # 4. Get token from cookie
269 # if not token:
270 # auth_cookie = cherrypy.request.cookie.get("Authorization")
271 # if auth_cookie:
272 # token = auth_cookie.value
273 return self.engine.authorize(token)
274 except EngineException as e:
275 if cherrypy.session.get('Authorization'):
276 del cherrypy.session['Authorization']
277 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="{}"'.format(e)
278 raise
279
280 def _format_in(self, kwargs):
281 try:
282 indata = None
283 if cherrypy.request.body.length:
284 error_text = "Invalid input format "
285
286 if "Content-Type" in cherrypy.request.headers:
287 if "application/json" in cherrypy.request.headers["Content-Type"]:
288 error_text = "Invalid json format "
289 indata = json.load(self.reader(cherrypy.request.body))
290 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
291 error_text = "Invalid yaml format "
292 indata = yaml.load(cherrypy.request.body)
293 elif "application/binary" in cherrypy.request.headers["Content-Type"] or \
294 "application/gzip" in cherrypy.request.headers["Content-Type"] or \
tiernof27c79b2018-03-12 17:08:42 +0100295 "application/zip" in cherrypy.request.headers["Content-Type"] or \
296 "text/plain" in cherrypy.request.headers["Content-Type"]:
297 indata = cherrypy.request.body # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100298 elif "multipart/form-data" in cherrypy.request.headers["Content-Type"]:
299 if "descriptor_file" in kwargs:
300 filecontent = kwargs.pop("descriptor_file")
301 if not filecontent.file:
302 raise NbiException("empty file or content", HTTPStatus.BAD_REQUEST)
tiernof27c79b2018-03-12 17:08:42 +0100303 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100304 if filecontent.content_type.value:
305 cherrypy.request.headers["Content-Type"] = filecontent.content_type.value
306 else:
307 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
308 # "Only 'Content-Type' of type 'application/json' or
309 # 'application/yaml' for input format are available")
310 error_text = "Invalid yaml format "
311 indata = yaml.load(cherrypy.request.body)
312 else:
313 error_text = "Invalid yaml format "
314 indata = yaml.load(cherrypy.request.body)
315 if not indata:
316 indata = {}
317
tiernoc94c3df2018-02-09 15:38:54 +0100318 format_yaml = False
319 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
320 format_yaml = True
321
322 for k, v in kwargs.items():
323 if isinstance(v, str):
324 if v == "":
325 kwargs[k] = None
326 elif format_yaml:
327 try:
328 kwargs[k] = yaml.load(v)
tiernoe1281182018-05-22 12:24:36 +0200329 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100330 pass
331 elif k.endswith(".gt") or k.endswith(".lt") or k.endswith(".gte") or k.endswith(".lte"):
332 try:
333 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200334 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100335 try:
336 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200337 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100338 pass
339 elif v.find(",") > 0:
340 kwargs[k] = v.split(",")
341 elif isinstance(v, (list, tuple)):
342 for index in range(0, len(v)):
343 if v[index] == "":
344 v[index] = None
345 elif format_yaml:
346 try:
347 v[index] = yaml.load(v[index])
tiernoe1281182018-05-22 12:24:36 +0200348 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100349 pass
350
tiernof27c79b2018-03-12 17:08:42 +0100351 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100352 except (ValueError, yaml.YAMLError) as exc:
353 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
354 except KeyError as exc:
355 raise NbiException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
tiernob92094f2018-05-11 13:44:22 +0200356 except Exception as exc:
357 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100358
359 @staticmethod
tiernof27c79b2018-03-12 17:08:42 +0100360 def _format_out(data, session=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100361 """
362 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100363 :param data: response to be sent. Can be a dict, text or file
tiernoc94c3df2018-02-09 15:38:54 +0100364 :param session:
tiernof27c79b2018-03-12 17:08:42 +0100365 :param _format: The format to be set as Content-Type ir data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100366 :return: None
367 """
tierno0f98af52018-03-19 10:28:22 +0100368 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100369 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100370 if accept and "text/html" in accept:
371 return html.format(data, cherrypy.request, cherrypy.response, session)
tierno09c073e2018-04-26 13:36:48 +0200372 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100373 return
374 elif hasattr(data, "read"): # file object
375 if _format:
376 cherrypy.response.headers["Content-Type"] = _format
377 elif "b" in data.mode: # binariy asssumig zip
378 cherrypy.response.headers["Content-Type"] = 'application/zip'
379 else:
380 cherrypy.response.headers["Content-Type"] = 'text/plain'
381 # TODO check that cherrypy close file. If not implement pending things to close per thread next
382 return data
tierno0f98af52018-03-19 10:28:22 +0100383 if accept:
tiernoc94c3df2018-02-09 15:38:54 +0100384 if "application/json" in accept:
385 cherrypy.response.headers["Content-Type"] = 'application/json; charset=utf-8'
386 a = json.dumps(data, indent=4) + "\n"
387 return a.encode("utf8")
388 elif "text/html" in accept:
389 return html.format(data, cherrypy.request, cherrypy.response, session)
390
tiernof27c79b2018-03-12 17:08:42 +0100391 elif "application/yaml" in accept or "*/*" in accept or "text/plain" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100392 pass
393 else:
394 raise cherrypy.HTTPError(HTTPStatus.NOT_ACCEPTABLE.value,
395 "Only 'Accept' of type 'application/json' or 'application/yaml' "
396 "for output format are available")
397 cherrypy.response.headers["Content-Type"] = 'application/yaml'
398 return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False,
399 encoding='utf-8', allow_unicode=True) # , canonical=True, default_style='"'
400
401 @cherrypy.expose
402 def index(self, *args, **kwargs):
403 session = None
404 try:
405 if cherrypy.request.method == "GET":
406 session = self._authorization()
407 outdata = "Index page"
408 else:
409 raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value,
tierno2236d202018-05-16 19:05:16 +0200410 "Method {} not allowed for tokens".format(cherrypy.request.method))
tiernoc94c3df2018-02-09 15:38:54 +0100411
412 return self._format_out(outdata, session)
413
414 except EngineException as e:
415 cherrypy.log("index Exception {}".format(e))
416 cherrypy.response.status = e.http_code.value
417 return self._format_out("Welcome to OSM!", session)
418
419 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200420 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200421 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200422 global __version__, version_date
423 try:
424 if cherrypy.request.method != "GET":
425 raise NbiException("Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED)
426 elif args or kwargs:
427 raise NbiException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED)
428 return __version__ + " " + version_date
429 except NbiException as e:
430 cherrypy.response.status = e.http_code.value
431 problem_details = {
432 "code": e.http_code.name,
433 "status": e.http_code.value,
434 "detail": str(e),
435 }
436 return self._format_out(problem_details, None)
437
438 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100439 def token(self, method, token_id=None, kwargs=None):
tiernoc94c3df2018-02-09 15:38:54 +0100440 session = None
441 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +0100442 indata = self._format_in(kwargs)
443 if not isinstance(indata, dict):
444 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100445 try:
tiernoc94c3df2018-02-09 15:38:54 +0100446 if method == "GET":
447 session = self._authorization()
tiernof27c79b2018-03-12 17:08:42 +0100448 if token_id:
449 outdata = self.engine.get_token(session, token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100450 else:
451 outdata = self.engine.get_token_list(session)
452 elif method == "POST":
453 try:
454 session = self._authorization()
tiernoe1281182018-05-22 12:24:36 +0200455 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100456 session = None
457 if kwargs:
458 indata.update(kwargs)
459 outdata = self.engine.new_token(session, indata, cherrypy.request.remote)
460 session = outdata
461 cherrypy.session['Authorization'] = outdata["_id"]
tierno0f98af52018-03-19 10:28:22 +0100462 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
tiernoc94c3df2018-02-09 15:38:54 +0100463 # cherrypy.response.cookie["Authorization"] = outdata["id"]
464 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
465 elif method == "DELETE":
tiernof27c79b2018-03-12 17:08:42 +0100466 if not token_id and "id" in kwargs:
tiernoc94c3df2018-02-09 15:38:54 +0100467 token_id = kwargs["id"]
tiernof27c79b2018-03-12 17:08:42 +0100468 elif not token_id:
tiernoc94c3df2018-02-09 15:38:54 +0100469 session = self._authorization()
470 token_id = session["_id"]
471 outdata = self.engine.del_token(token_id)
472 session = None
473 cherrypy.session['Authorization'] = "logout"
474 # cherrypy.response.cookie["Authorization"] = token_id
475 # cherrypy.response.cookie["Authorization"]['expires'] = 0
476 else:
477 raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
478 return self._format_out(outdata, session)
479 except (NbiException, EngineException, DbException) as e:
480 cherrypy.log("tokens Exception {}".format(e))
481 cherrypy.response.status = e.http_code.value
482 problem_details = {
483 "code": e.http_code.name,
484 "status": e.http_code.value,
485 "detail": str(e),
486 }
487 return self._format_out(problem_details, session)
488
489 @cherrypy.expose
490 def test(self, *args, **kwargs):
491 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +0100492 if args and args[0] == "help":
493 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nprune\nlogin\nlogin2\n"\
tiernoe1281182018-05-22 12:24:36 +0200494 "sleep/<time>\nmessage/topic\n</pre></html>"
tiernof27c79b2018-03-12 17:08:42 +0100495
496 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +0100497 try:
498 # self.engine.load_dbase(cherrypy.request.app.config)
499 self.engine.create_admin()
500 return "Done. User 'admin', password 'admin' created"
501 except Exception:
502 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
503 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +0100504 elif args and args[0] == "file":
505 return cherrypy.lib.static.serve_file(cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1],
506 "text/plain", "attachment")
507 elif args and args[0] == "file2":
508 f_path = cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1]
509 f = open(f_path, "r")
510 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100511 return f
tierno55945e72018-04-06 16:40:27 +0200512
tiernof27c79b2018-03-12 17:08:42 +0100513 elif len(args) == 2 and args[0] == "db-clear":
514 return self.engine.del_item_list({"project_id": "admin"}, args[1], {})
tiernoc94c3df2018-02-09 15:38:54 +0100515 elif args and args[0] == "prune":
516 return self.engine.prune()
517 elif args and args[0] == "login":
518 if not cherrypy.request.headers.get("Authorization"):
519 cherrypy.response.headers["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
520 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
521 elif args and args[0] == "login2":
522 if not cherrypy.request.headers.get("Authorization"):
523 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
524 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
525 elif args and args[0] == "sleep":
526 sleep_time = 5
527 try:
528 sleep_time = int(args[1])
529 except Exception:
530 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
531 return self._format_out("Database already initialized")
532 thread_info = cherrypy.thread_data
533 print(thread_info)
534 time.sleep(sleep_time)
535 # thread_info
536 elif len(args) >= 2 and args[0] == "message":
537 topic = args[1]
tierno55945e72018-04-06 16:40:27 +0200538 return_text = "<html><pre>{} ->\n".format(topic)
tiernoc94c3df2018-02-09 15:38:54 +0100539 try:
tierno55945e72018-04-06 16:40:27 +0200540 if cherrypy.request.method == 'POST':
541 to_send = yaml.load(cherrypy.request.body)
542 for k, v in to_send.items():
543 self.engine.msg.write(topic, k, v)
544 return_text += " {}: {}\n".format(k, v)
545 elif cherrypy.request.method == 'GET':
546 for k, v in kwargs.items():
547 self.engine.msg.write(topic, k, yaml.load(v))
548 return_text += " {}: {}\n".format(k, yaml.load(v))
tiernoc94c3df2018-02-09 15:38:54 +0100549 except Exception as e:
tierno55945e72018-04-06 16:40:27 +0200550 return_text += "Error: " + str(e)
551 return_text += "</pre></html>\n"
552 return return_text
tiernoc94c3df2018-02-09 15:38:54 +0100553
554 return_text = (
555 "<html><pre>\nheaders:\n args: {}\n".format(args) +
556 " kwargs: {}\n".format(kwargs) +
557 " headers: {}\n".format(cherrypy.request.headers) +
558 " path_info: {}\n".format(cherrypy.request.path_info) +
559 " query_string: {}\n".format(cherrypy.request.query_string) +
560 " session: {}\n".format(cherrypy.session) +
561 " cookie: {}\n".format(cherrypy.request.cookie) +
562 " method: {}\n".format(cherrypy.request.method) +
563 " session: {}\n".format(cherrypy.session.get('fieldname')) +
564 " body:\n")
565 return_text += " length: {}\n".format(cherrypy.request.body.length)
566 if cherrypy.request.body.length:
567 return_text += " content: {}\n".format(
568 str(cherrypy.request.body.read(int(cherrypy.request.headers.get('Content-Length', 0)))))
569 if thread_info:
570 return_text += "thread: {}\n".format(thread_info)
571 return_text += "</pre></html>"
572 return return_text
573
tiernof27c79b2018-03-12 17:08:42 +0100574 def _check_valid_url_method(self, method, *args):
575 if len(args) < 3:
576 raise NbiException("URL must contain at least 'topic/version/item'", HTTPStatus.METHOD_NOT_ALLOWED)
577
578 reference = self.valid_methods
579 for arg in args:
580 if arg is None:
581 break
582 if not isinstance(reference, dict):
583 raise NbiException("URL contains unexpected extra items '{}'".format(arg),
584 HTTPStatus.METHOD_NOT_ALLOWED)
585
586 if arg in reference:
587 reference = reference[arg]
588 elif "<ID>" in reference:
589 reference = reference["<ID>"]
590 elif "*" in reference:
591 reference = reference["*"]
592 break
593 else:
594 raise NbiException("Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED)
595 if "TODO" in reference and method in reference["TODO"]:
596 raise NbiException("Method {} not supported yet for this URL".format(method), HTTPStatus.NOT_IMPLEMENTED)
tierno2236d202018-05-16 19:05:16 +0200597 elif "METHODS" in reference and method not in reference["METHODS"]:
tiernof27c79b2018-03-12 17:08:42 +0100598 raise NbiException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
599 return
600
601 @staticmethod
602 def _set_location_header(topic, version, item, id):
603 """
604 Insert response header Location with the URL of created item base on URL params
605 :param topic:
606 :param version:
607 :param item:
608 :param id:
609 :return: None
610 """
611 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
612 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(topic, version, item, id)
613 return
614
tiernoc94c3df2018-02-09 15:38:54 +0100615 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100616 def default(self, topic=None, version=None, item=None, _id=None, item2=None, *args, **kwargs):
tiernoc94c3df2018-02-09 15:38:54 +0100617 session = None
tiernof27c79b2018-03-12 17:08:42 +0100618 outdata = None
619 _format = None
tierno0f98af52018-03-19 10:28:22 +0100620 method = "DONE"
621 engine_item = None
tiernodb9dc582018-06-20 17:27:29 +0200622 rollback = []
623 session = None
tiernoc94c3df2018-02-09 15:38:54 +0100624 try:
tiernof27c79b2018-03-12 17:08:42 +0100625 if not topic or not version or not item:
626 raise NbiException("URL must contain at least 'topic/version/item'", HTTPStatus.METHOD_NOT_ALLOWED)
627 if topic not in ("admin", "vnfpkgm", "nsd", "nslcm"):
628 raise NbiException("URL topic '{}' not supported".format(topic), HTTPStatus.METHOD_NOT_ALLOWED)
tiernoc94c3df2018-02-09 15:38:54 +0100629 if version != 'v1':
630 raise NbiException("URL version '{}' not supported".format(version), HTTPStatus.METHOD_NOT_ALLOWED)
631
tiernof27c79b2018-03-12 17:08:42 +0100632 if kwargs and "METHOD" in kwargs and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
633 method = kwargs.pop("METHOD")
634 else:
635 method = cherrypy.request.method
tiernob92094f2018-05-11 13:44:22 +0200636 if kwargs and "FORCE" in kwargs:
637 force = kwargs.pop("FORCE")
638 else:
639 force = False
tiernof27c79b2018-03-12 17:08:42 +0100640
641 self._check_valid_url_method(method, topic, version, item, _id, item2, *args)
642
643 if topic == "admin" and item == "tokens":
644 return self.token(method, _id, kwargs)
645
tiernoc94c3df2018-02-09 15:38:54 +0100646 # self.engine.load_dbase(cherrypy.request.app.config)
647 session = self._authorization()
tiernof27c79b2018-03-12 17:08:42 +0100648 indata = self._format_in(kwargs)
649 engine_item = item
650 if item == "subscriptions":
651 engine_item = topic + "_" + item
652 if item2:
653 engine_item = item2
tiernoc94c3df2018-02-09 15:38:54 +0100654
tiernof27c79b2018-03-12 17:08:42 +0100655 if topic == "nsd":
656 engine_item = "nsds"
657 elif topic == "vnfpkgm":
658 engine_item = "vnfds"
659 elif topic == "nslcm":
660 engine_item = "nsrs"
tierno65acb4d2018-04-06 16:42:40 +0200661 if item == "ns_lcm_op_occs":
662 engine_item = "nslcmops"
tiernof759d822018-06-11 18:54:54 +0200663 if item == "vnfrs" or item == "vnf_instances":
tierno0ffaa992018-05-09 13:21:56 +0200664 engine_item = "vnfrs"
tierno09c073e2018-04-26 13:36:48 +0200665 if engine_item == "vims": # TODO this is for backward compatibility, it will remove in the future
666 engine_item = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +0100667
668 if method == "GET":
tiernof27c79b2018-03-12 17:08:42 +0100669 if item2 in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd"):
670 if item2 in ("vnfd", "nsd"):
671 path = "$DESCRIPTOR"
672 elif args:
673 path = args
674 elif item2 == "artifacts":
675 path = ()
676 else:
677 path = None
678 file, _format = self.engine.get_file(session, engine_item, _id, path,
tierno2236d202018-05-16 19:05:16 +0200679 cherrypy.request.headers.get("Accept"))
tiernof27c79b2018-03-12 17:08:42 +0100680 outdata = file
681 elif not _id:
682 outdata = self.engine.get_item_list(session, engine_item, kwargs)
tiernoc94c3df2018-02-09 15:38:54 +0100683 else:
tiernof27c79b2018-03-12 17:08:42 +0100684 outdata = self.engine.get_item(session, engine_item, _id)
685 elif method == "POST":
686 if item in ("ns_descriptors_content", "vnf_packages_content"):
687 _id = cherrypy.request.headers.get("Transaction-Id")
688 if not _id:
tiernodb9dc582018-06-20 17:27:29 +0200689 _id = self.engine.new_item(rollback, session, engine_item, {}, None, cherrypy.request.headers,
tiernob92094f2018-05-11 13:44:22 +0200690 force=force)
tierno2236d202018-05-16 19:05:16 +0200691 completed = self.engine.upload_content(session, engine_item, _id, indata, kwargs,
692 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +0100693 if completed:
694 self._set_location_header(topic, version, item, _id)
695 else:
696 cherrypy.response.headers["Transaction-Id"] = _id
697 outdata = {"id": _id}
tierno65acb4d2018-04-06 16:42:40 +0200698 elif item == "ns_instances_content":
tiernodb9dc582018-06-20 17:27:29 +0200699 _id = self.engine.new_item(rollback, session, engine_item, indata, kwargs, force=force)
tierno0da52252018-06-27 15:47:22 +0200700 self.engine.ns_operation(rollback, session, _id, "instantiate", indata, None)
tiernof27c79b2018-03-12 17:08:42 +0100701 self._set_location_header(topic, version, item, _id)
tiernof27c79b2018-03-12 17:08:42 +0100702 outdata = {"id": _id}
tierno65acb4d2018-04-06 16:42:40 +0200703 elif item == "ns_instances" and item2:
tiernodb9dc582018-06-20 17:27:29 +0200704 _id = self.engine.ns_operation(rollback, session, _id, item2, indata, kwargs)
tierno65acb4d2018-04-06 16:42:40 +0200705 self._set_location_header(topic, version, "ns_lcm_op_occs", _id)
706 outdata = {"id": _id}
707 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +0100708 else:
tiernodb9dc582018-06-20 17:27:29 +0200709 _id = self.engine.new_item(rollback, session, engine_item, indata, kwargs, cherrypy.request.headers,
tiernob92094f2018-05-11 13:44:22 +0200710 force=force)
tiernof27c79b2018-03-12 17:08:42 +0100711 self._set_location_header(topic, version, item, _id)
712 outdata = {"id": _id}
tierno65acb4d2018-04-06 16:42:40 +0200713 # TODO form NsdInfo when item in ("ns_descriptors", "vnf_packages")
tiernof27c79b2018-03-12 17:08:42 +0100714 cherrypy.response.status = HTTPStatus.CREATED.value
tierno09c073e2018-04-26 13:36:48 +0200715
tiernoc94c3df2018-02-09 15:38:54 +0100716 elif method == "DELETE":
717 if not _id:
tiernof27c79b2018-03-12 17:08:42 +0100718 outdata = self.engine.del_item_list(session, engine_item, kwargs)
tierno09c073e2018-04-26 13:36:48 +0200719 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +0100720 else: # len(args) > 1
tiernob62b7ac2018-05-28 16:50:20 +0200721 if item == "ns_instances_content" and not force:
tiernodb9dc582018-06-20 17:27:29 +0200722 opp_id = self.engine.ns_operation(rollback, session, _id, "terminate", {"autoremove": True},
723 None)
tierno3ace63c2018-05-03 17:51:43 +0200724 outdata = {"_id": opp_id}
tierno09c073e2018-04-26 13:36:48 +0200725 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tierno65acb4d2018-04-06 16:42:40 +0200726 else:
tierno65acb4d2018-04-06 16:42:40 +0200727 self.engine.del_item(session, engine_item, _id, force)
tierno09c073e2018-04-26 13:36:48 +0200728 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
729 if engine_item in ("vim_accounts", "sdns"):
730 cherrypy.response.status = HTTPStatus.ACCEPTED.value
731
tierno7ae10112018-05-18 14:36:02 +0200732 elif method in ("PUT", "PATCH"):
tiernof27c79b2018-03-12 17:08:42 +0100733 if not indata and not kwargs:
tiernoc94c3df2018-02-09 15:38:54 +0100734 raise NbiException("Nothing to update. Provide payload and/or query string",
735 HTTPStatus.BAD_REQUEST)
tierno7ae10112018-05-18 14:36:02 +0200736 if item2 in ("nsd_content", "package_content") and method == "PUT":
tierno2236d202018-05-16 19:05:16 +0200737 completed = self.engine.upload_content(session, engine_item, _id, indata, kwargs,
738 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +0100739 if not completed:
740 cherrypy.response.headers["Transaction-Id"] = id
tierno09c073e2018-04-26 13:36:48 +0200741 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100742 outdata = None
743 else:
tiernob92094f2018-05-11 13:44:22 +0200744 outdata = {"id": self.engine.edit_item(session, engine_item, _id, indata, kwargs, force=force)}
tiernoc94c3df2018-02-09 15:38:54 +0100745 else:
746 raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tiernof27c79b2018-03-12 17:08:42 +0100747 return self._format_out(outdata, session, _format)
tierno0f98af52018-03-19 10:28:22 +0100748 except (NbiException, EngineException, DbException, FsException, MsgException) as e:
tiernoc94c3df2018-02-09 15:38:54 +0100749 cherrypy.log("Exception {}".format(e))
750 cherrypy.response.status = e.http_code.value
tierno3ace63c2018-05-03 17:51:43 +0200751 if hasattr(outdata, "close"): # is an open file
752 outdata.close()
tiernodb9dc582018-06-20 17:27:29 +0200753 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +0200754 try:
tiernodb9dc582018-06-20 17:27:29 +0200755 self.engine.del_item(**rollback_item, session=session, force=True)
tierno3ace63c2018-05-03 17:51:43 +0200756 except Exception as e2:
tiernodb9dc582018-06-20 17:27:29 +0200757 cherrypy.log("Rollback Exception {}: {}".format(rollback_item, e2))
tierno0f98af52018-03-19 10:28:22 +0100758 error_text = str(e)
759 if isinstance(e, MsgException):
760 error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
761 engine_item[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +0100762 problem_details = {
763 "code": e.http_code.name,
764 "status": e.http_code.value,
765 "detail": str(e),
766 }
767 return self._format_out(problem_details, session)
768 # raise cherrypy.HTTPError(e.http_code.value, str(e))
769
770
771# def validate_password(realm, username, password):
772# cherrypy.log("realm "+ str(realm))
773# if username == "admin" and password == "admin":
774# return True
775# return False
776
777
778def _start_service():
779 """
780 Callback function called when cherrypy.engine starts
781 Override configuration with env variables
782 Set database, storage, message configuration
783 Init database with admin/admin user password
784 """
785 cherrypy.log.error("Starting osm_nbi")
786 # update general cherrypy configuration
787 update_dict = {}
788
789 engine_config = cherrypy.tree.apps['/osm'].config
790 for k, v in environ.items():
791 if not k.startswith("OSMNBI_"):
792 continue
tiernoe1281182018-05-22 12:24:36 +0200793 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +0100794 if not k2:
795 continue
796 try:
797 # update static configuration
798 if k == 'OSMNBI_STATIC_DIR':
799 engine_config["/static"]['tools.staticdir.dir'] = v
800 engine_config["/static"]['tools.staticdir.on'] = True
801 elif k == 'OSMNBI_SOCKET_PORT' or k == 'OSMNBI_SERVER_PORT':
802 update_dict['server.socket_port'] = int(v)
803 elif k == 'OSMNBI_SOCKET_HOST' or k == 'OSMNBI_SERVER_HOST':
804 update_dict['server.socket_host'] = v
tiernof5298be2018-05-16 14:43:57 +0200805 elif k1 in ("server", "test", "auth", "log"):
806 update_dict[k1 + '.' + k2] = v
tiernoc94c3df2018-02-09 15:38:54 +0100807 elif k1 in ("message", "database", "storage"):
tiernof5298be2018-05-16 14:43:57 +0200808 # k2 = k2.replace('_', '.')
tiernoc94c3df2018-02-09 15:38:54 +0100809 if k2 == "port":
810 engine_config[k1][k2] = int(v)
811 else:
812 engine_config[k1][k2] = v
813 except ValueError as e:
814 cherrypy.log.error("Ignoring environ '{}': " + str(e))
815 except Exception as e:
816 cherrypy.log.warn("skipping environ '{}' on exception '{}'".format(k, e))
817
818 if update_dict:
819 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +0200820 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +0100821
822 # logging cherrypy
823 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
824 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
825 logger_server = logging.getLogger("cherrypy.error")
826 logger_access = logging.getLogger("cherrypy.access")
827 logger_cherry = logging.getLogger("cherrypy")
828 logger_nbi = logging.getLogger("nbi")
829
tiernof5298be2018-05-16 14:43:57 +0200830 if "log.file" in engine_config["global"]:
831 file_handler = logging.handlers.RotatingFileHandler(engine_config["global"]["log.file"],
tiernoc94c3df2018-02-09 15:38:54 +0100832 maxBytes=100e6, backupCount=9, delay=0)
833 file_handler.setFormatter(log_formatter_simple)
834 logger_cherry.addHandler(file_handler)
835 logger_nbi.addHandler(file_handler)
836 else:
837 for format_, logger in {"nbi.server": logger_server,
838 "nbi.access": logger_access,
839 "%(name)s %(filename)s:%(lineno)s": logger_nbi
840 }.items():
841 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
842 log_formatter_cherry = logging.Formatter(log_format_cherry, datefmt='%Y-%m-%dT%H:%M:%S')
843 str_handler = logging.StreamHandler()
844 str_handler.setFormatter(log_formatter_cherry)
845 logger.addHandler(str_handler)
846
tiernof5298be2018-05-16 14:43:57 +0200847 if engine_config["global"].get("log.level"):
848 logger_cherry.setLevel(engine_config["global"]["log.level"])
849 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +0100850
851 # logging other modules
852 for k1, logname in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
853 engine_config[k1]["logger_name"] = logname
854 logger_module = logging.getLogger(logname)
855 if "logfile" in engine_config[k1]:
856 file_handler = logging.handlers.RotatingFileHandler(engine_config[k1]["logfile"],
tierno2236d202018-05-16 19:05:16 +0200857 maxBytes=100e6, backupCount=9, delay=0)
tiernoc94c3df2018-02-09 15:38:54 +0100858 file_handler.setFormatter(log_formatter_simple)
859 logger_module.addHandler(file_handler)
860 if "loglevel" in engine_config[k1]:
861 logger_module.setLevel(engine_config[k1]["loglevel"])
862 # TODO add more entries, e.g.: storage
863 cherrypy.tree.apps['/osm'].root.engine.start(engine_config)
864 try:
tierno4a946e42018-04-12 17:48:49 +0200865 cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version)
tiernoc94c3df2018-02-09 15:38:54 +0100866 except EngineException:
867 pass
868 # getenv('OSMOPENMANO_TENANT', None)
869
870
871def _stop_service():
872 """
873 Callback function called when cherrypy.engine stops
874 TODO: Ending database connections.
875 """
876 cherrypy.tree.apps['/osm'].root.engine.stop()
877 cherrypy.log.error("Stopping osm_nbi")
878
tierno2236d202018-05-16 19:05:16 +0200879
tiernof5298be2018-05-16 14:43:57 +0200880def nbi(config_file):
tiernoc94c3df2018-02-09 15:38:54 +0100881 # conf = {
882 # '/': {
883 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
884 # 'tools.sessions.on': True,
885 # 'tools.response_headers.on': True,
886 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
887 # }
888 # }
889 # cherrypy.Server.ssl_module = 'builtin'
890 # cherrypy.Server.ssl_certificate = "http/cert.pem"
891 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
892 # cherrypy.Server.thread_pool = 10
893 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
894
895 # cherrypy.config.update({'tools.auth_basic.on': True,
896 # 'tools.auth_basic.realm': 'localhost',
897 # 'tools.auth_basic.checkpassword': validate_password})
898 cherrypy.engine.subscribe('start', _start_service)
899 cherrypy.engine.subscribe('stop', _stop_service)
tiernof5298be2018-05-16 14:43:57 +0200900 cherrypy.quickstart(Server(), '/osm', config_file)
901
902
903def usage():
904 print("""Usage: {} [options]
905 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
906 -h|--help: shows this help
907 """.format(sys.argv[0]))
tierno2236d202018-05-16 19:05:16 +0200908 # --log-socket-host HOST: send logs to this host")
909 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +0100910
911
912if __name__ == '__main__':
tiernof5298be2018-05-16 14:43:57 +0200913 try:
914 # load parameters and configuration
915 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
916 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
917 config_file = None
918 for o, a in opts:
919 if o in ("-h", "--help"):
920 usage()
921 sys.exit()
922 elif o in ("-c", "--config"):
923 config_file = a
924 # elif o == "--log-socket-port":
925 # log_socket_port = a
926 # elif o == "--log-socket-host":
927 # log_socket_host = a
928 # elif o == "--log-file":
929 # log_file = a
930 else:
931 assert False, "Unhandled option"
932 if config_file:
933 if not path.isfile(config_file):
934 print("configuration file '{}' that not exist".format(config_file), file=sys.stderr)
935 exit(1)
936 else:
937 for config_file in (__file__[:__file__.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
938 if path.isfile(config_file):
939 break
940 else:
941 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys.stderr)
942 exit(1)
943 nbi(config_file)
944 except getopt.GetoptError as e:
945 print(str(e), file=sys.stderr)
946 # usage()
947 exit(1)