blob: fa2f0432b2c6838fc0a16afc8f3e60a5e611a571 [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
Eduardo Sousa2f988212018-07-26 01:04:11 +010013
Eduardo Sousa819d34c2018-07-31 01:20:02 +010014from authconn import AuthException
Eduardo Sousa2f988212018-07-26 01:04:11 +010015from auth import Authenticator
tiernoc94c3df2018-02-09 15:38:54 +010016from engine import Engine, EngineException
tiernoa8d63632018-05-10 13:12:32 +020017from osm_common.dbbase import DbException
18from osm_common.fsbase import FsException
19from osm_common.msgbase import MsgException
tiernoc94c3df2018-02-09 15:38:54 +010020from http import HTTPStatus
tiernoc94c3df2018-02-09 15:38:54 +010021from codecs import getreader
tiernof5298be2018-05-16 14:43:57 +020022from os import environ, path
tiernoc94c3df2018-02-09 15:38:54 +010023
24__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
tiernodfe09572018-04-24 10:41:10 +020025
26# TODO consider to remove and provide version using the static version file
27__version__ = "0.1.3"
tierno55945e72018-04-06 16:40:27 +020028version_date = "Apr 2018"
tierno4a946e42018-04-12 17:48:49 +020029database_version = '1.0'
Eduardo Sousa819d34c2018-07-31 01:20:02 +010030auth_database_version = '1.0'
tiernoc94c3df2018-02-09 15:38:54 +010031
32"""
tiernof27c79b2018-03-12 17:08:42 +010033North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
tiernoc94c3df2018-02-09 15:38:54 +010034URL: /osm GET POST PUT DELETE PATCH
tiernof27c79b2018-03-12 17:08:42 +010035 /nsd/v1 O O
tierno2236d202018-05-16 19:05:16 +020036 /ns_descriptors_content O O
37 /<nsdInfoId> O O O O
tiernoc94c3df2018-02-09 15:38:54 +010038 /ns_descriptors O5 O5
39 /<nsdInfoId> O5 O5 5
40 /nsd_content O5 O5
tiernof27c79b2018-03-12 17:08:42 +010041 /nsd O
42 /artifacts[/<artifactPath>] O
tiernoc94c3df2018-02-09 15:38:54 +010043 /pnf_descriptors 5 5
44 /<pnfdInfoId> 5 5 5
45 /pnfd_content 5 5
tiernof27c79b2018-03-12 17:08:42 +010046 /subscriptions 5 5
47 /<subscriptionId> 5 X
tiernoc94c3df2018-02-09 15:38:54 +010048
49 /vnfpkgm/v1
tierno55945e72018-04-06 16:40:27 +020050 /vnf_packages_content O O
tierno2236d202018-05-16 19:05:16 +020051 /<vnfPkgId> O O
tiernoc94c3df2018-02-09 15:38:54 +010052 /vnf_packages O5 O5
53 /<vnfPkgId> O5 O5 5
tiernoc94c3df2018-02-09 15:38:54 +010054 /package_content O5 O5
55 /upload_from_uri X
tiernof27c79b2018-03-12 17:08:42 +010056 /vnfd O5
57 /artifacts[/<artifactPath>] O5
58 /subscriptions X X
59 /<subscriptionId> X X
tiernoc94c3df2018-02-09 15:38:54 +010060
61 /nslcm/v1
tiernof27c79b2018-03-12 17:08:42 +010062 /ns_instances_content O O
tierno2236d202018-05-16 19:05:16 +020063 /<nsInstanceId> O O
tiernof27c79b2018-03-12 17:08:42 +010064 /ns_instances 5 5
tierno95692442018-05-24 18:05:28 +020065 /<nsInstanceId> O5 O5
tierno65acb4d2018-04-06 16:42:40 +020066 instantiate O5
67 terminate O5
68 action O
69 scale O5
70 heal 5
tiernoc94c3df2018-02-09 15:38:54 +010071 /ns_lcm_op_occs 5 5
72 /<nsLcmOpOccId> 5 5 5
73 TO BE COMPLETED 5 5
tiernof759d822018-06-11 18:54:54 +020074 /vnf_instances (also vnfrs for compatibility) O
75 /<vnfInstanceId> O
tiernof27c79b2018-03-12 17:08:42 +010076 /subscriptions 5 5
77 /<subscriptionId> 5 X
78 /admin/v1
79 /tokens O O
tierno2236d202018-05-16 19:05:16 +020080 /<id> O O
tiernof27c79b2018-03-12 17:08:42 +010081 /users O O
tiernocd54a4a2018-09-12 16:40:35 +020082 /<id> O O O O
tiernof27c79b2018-03-12 17:08:42 +010083 /projects O O
tierno2236d202018-05-16 19:05:16 +020084 /<id> O O
tierno09c073e2018-04-26 13:36:48 +020085 /vims_accounts (also vims for compatibility) O O
tierno2236d202018-05-16 19:05:16 +020086 /<id> O O O
tierno0f98af52018-03-19 10:28:22 +010087 /sdns O O
tierno2236d202018-05-16 19:05:16 +020088 /<id> O O O
tiernoc94c3df2018-02-09 15:38:54 +010089
tierno2236d202018-05-16 19:05:16 +020090query string:
91 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
92 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
93 item of the array, that is, pass if any item of the array pass the filter.
94 It allows both ne and neq for not equal
95 TODO: 4.3.3 Attribute selectors
96 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
tiernoc94c3df2018-02-09 15:38:54 +010097 (none) … same as “exclude_default”
98 all_fields … all attributes.
tierno2236d202018-05-16 19:05:16 +020099 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
100 conditionally mandatory, and that are not provided in <list>.
101 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
102 are not conditionally mandatory, and that are provided in <list>.
103 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
104 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
105 the particular resource
106 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
107 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
108 present specification for the particular resource, but that are not part of <list>
tiernoc94c3df2018-02-09 15:38:54 +0100109Header field name Reference Example Descriptions
110 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
111 This header field shall be present if the response is expected to have a non-empty message body.
112 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
113 This header field shall be present if the request has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200114 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
115 Details are specified in clause 4.5.3.
tiernoc94c3df2018-02-09 15:38:54 +0100116 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
117Header field name Reference Example Descriptions
118 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
119 This header field shall be present if the response has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200120 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
121 new resource has been created.
tiernoc94c3df2018-02-09 15:38:54 +0100122 This header field shall be present if the response status code is 201 or 3xx.
tierno2236d202018-05-16 19:05:16 +0200123 In the present document this header field is also used if the response status code is 202 and a new resource was
124 created.
125 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
126 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
127 token.
128 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
129 certain resources.
130 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
131 response, and the total length of the file.
tiernoc94c3df2018-02-09 15:38:54 +0100132 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
tiernoc94c3df2018-02-09 15:38:54 +0100133"""
134
135
136class NbiException(Exception):
137
138 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
139 Exception.__init__(self, message)
140 self.http_code = http_code
141
142
143class Server(object):
144 instance = 0
145 # to decode bytes to str
146 reader = getreader("utf-8")
147
148 def __init__(self):
149 self.instance += 1
150 self.engine = Engine()
Eduardo Sousad1b525d2018-10-04 04:24:18 +0100151 self.authenticator = Authenticator()
tiernof27c79b2018-03-12 17:08:42 +0100152 self.valid_methods = { # contains allowed URL and methods
153 "admin": {
154 "v1": {
tierno0f98af52018-03-19 10:28:22 +0100155 "tokens": {"METHODS": ("GET", "POST", "DELETE"),
tierno2236d202018-05-16 19:05:16 +0200156 "<ID>": {"METHODS": ("GET", "DELETE")}
157 },
tierno0f98af52018-03-19 10:28:22 +0100158 "users": {"METHODS": ("GET", "POST"),
tiernocd54a4a2018-09-12 16:40:35 +0200159 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200160 },
tierno0f98af52018-03-19 10:28:22 +0100161 "projects": {"METHODS": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200162 "<ID>": {"METHODS": ("GET", "DELETE")}
163 },
tierno0f98af52018-03-19 10:28:22 +0100164 "vims": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200165 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200166 },
tierno09c073e2018-04-26 13:36:48 +0200167 "vim_accounts": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200168 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200169 },
tierno0f98af52018-03-19 10:28:22 +0100170 "sdns": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200171 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200172 },
tiernof27c79b2018-03-12 17:08:42 +0100173 }
174 },
175 "nsd": {
176 "v1": {
tierno2236d202018-05-16 19:05:16 +0200177 "ns_descriptors_content": {"METHODS": ("GET", "POST"),
178 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
179 },
180 "ns_descriptors": {"METHODS": ("GET", "POST"),
181 "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH",
182 "nsd_content": {"METHODS": ("GET", "PUT")},
183 "nsd": {"METHODS": "GET"}, # descriptor inside package
184 "artifacts": {"*": {"METHODS": "GET"}}
185 }
186 },
tiernof27c79b2018-03-12 17:08:42 +0100187 "pnf_descriptors": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200188 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
189 "pnfd_content": {"TODO": ("GET", "PUT")}
190 }
191 },
tiernof27c79b2018-03-12 17:08:42 +0100192 "subscriptions": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200193 "<ID>": {"TODO": ("GET", "DELETE")}
194 },
tiernof27c79b2018-03-12 17:08:42 +0100195 }
196 },
197 "vnfpkgm": {
198 "v1": {
tierno2236d202018-05-16 19:05:16 +0200199 "vnf_packages_content": {"METHODS": ("GET", "POST"),
200 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
201 },
202 "vnf_packages": {"METHODS": ("GET", "POST"),
tiernoaae4dc42018-06-11 18:55:50 +0200203 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
tierno2236d202018-05-16 19:05:16 +0200204 "package_content": {"METHODS": ("GET", "PUT"), # package
205 "upload_from_uri": {"TODO": "POST"}
206 },
207 "vnfd": {"METHODS": "GET"}, # descriptor inside package
208 "artifacts": {"*": {"METHODS": "GET"}}
209 }
210 },
tiernof27c79b2018-03-12 17:08:42 +0100211 "subscriptions": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200212 "<ID>": {"TODO": ("GET", "DELETE")}
213 },
tiernof27c79b2018-03-12 17:08:42 +0100214 }
215 },
216 "nslcm": {
217 "v1": {
218 "ns_instances_content": {"METHODS": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200219 "<ID>": {"METHODS": ("GET", "DELETE")}
220 },
tierno65acb4d2018-04-06 16:42:40 +0200221 "ns_instances": {"METHODS": ("GET", "POST"),
tierno95692442018-05-24 18:05:28 +0200222 "<ID>": {"METHODS": ("GET", "DELETE"),
tiernof759d822018-06-11 18:54:54 +0200223 "scale": {"METHODS": "POST"},
tierno2236d202018-05-16 19:05:16 +0200224 "terminate": {"METHODS": "POST"},
225 "instantiate": {"METHODS": "POST"},
226 "action": {"METHODS": "POST"},
227 }
228 },
tierno65acb4d2018-04-06 16:42:40 +0200229 "ns_lcm_op_occs": {"METHODS": "GET",
tierno2236d202018-05-16 19:05:16 +0200230 "<ID>": {"METHODS": "GET"},
231 },
tierno0ffaa992018-05-09 13:21:56 +0200232 "vnfrs": {"METHODS": ("GET"),
tierno2236d202018-05-16 19:05:16 +0200233 "<ID>": {"METHODS": ("GET")}
234 },
tiernof759d822018-06-11 18:54:54 +0200235 "vnf_instances": {"METHODS": ("GET"),
236 "<ID>": {"METHODS": ("GET")}
237 },
tiernof27c79b2018-03-12 17:08:42 +0100238 }
239 },
240 }
tiernoc94c3df2018-02-09 15:38:54 +0100241
tiernoc94c3df2018-02-09 15:38:54 +0100242 def _format_in(self, kwargs):
243 try:
244 indata = None
245 if cherrypy.request.body.length:
246 error_text = "Invalid input format "
247
248 if "Content-Type" in cherrypy.request.headers:
249 if "application/json" in cherrypy.request.headers["Content-Type"]:
250 error_text = "Invalid json format "
251 indata = json.load(self.reader(cherrypy.request.body))
252 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
253 error_text = "Invalid yaml format "
254 indata = yaml.load(cherrypy.request.body)
255 elif "application/binary" in cherrypy.request.headers["Content-Type"] or \
256 "application/gzip" in cherrypy.request.headers["Content-Type"] or \
tiernof27c79b2018-03-12 17:08:42 +0100257 "application/zip" in cherrypy.request.headers["Content-Type"] or \
258 "text/plain" in cherrypy.request.headers["Content-Type"]:
259 indata = cherrypy.request.body # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100260 elif "multipart/form-data" in cherrypy.request.headers["Content-Type"]:
261 if "descriptor_file" in kwargs:
262 filecontent = kwargs.pop("descriptor_file")
263 if not filecontent.file:
264 raise NbiException("empty file or content", HTTPStatus.BAD_REQUEST)
tiernof27c79b2018-03-12 17:08:42 +0100265 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100266 if filecontent.content_type.value:
267 cherrypy.request.headers["Content-Type"] = filecontent.content_type.value
268 else:
269 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
270 # "Only 'Content-Type' of type 'application/json' or
271 # 'application/yaml' for input format are available")
272 error_text = "Invalid yaml format "
273 indata = yaml.load(cherrypy.request.body)
274 else:
275 error_text = "Invalid yaml format "
276 indata = yaml.load(cherrypy.request.body)
277 if not indata:
278 indata = {}
279
tiernoc94c3df2018-02-09 15:38:54 +0100280 format_yaml = False
281 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
282 format_yaml = True
283
284 for k, v in kwargs.items():
285 if isinstance(v, str):
286 if v == "":
287 kwargs[k] = None
288 elif format_yaml:
289 try:
290 kwargs[k] = yaml.load(v)
tiernoe1281182018-05-22 12:24:36 +0200291 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100292 pass
293 elif k.endswith(".gt") or k.endswith(".lt") or k.endswith(".gte") or k.endswith(".lte"):
294 try:
295 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200296 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100297 try:
298 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200299 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100300 pass
301 elif v.find(",") > 0:
302 kwargs[k] = v.split(",")
303 elif isinstance(v, (list, tuple)):
304 for index in range(0, len(v)):
305 if v[index] == "":
306 v[index] = None
307 elif format_yaml:
308 try:
309 v[index] = yaml.load(v[index])
tiernoe1281182018-05-22 12:24:36 +0200310 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100311 pass
312
tiernof27c79b2018-03-12 17:08:42 +0100313 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100314 except (ValueError, yaml.YAMLError) as exc:
315 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
316 except KeyError as exc:
317 raise NbiException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
tiernob92094f2018-05-11 13:44:22 +0200318 except Exception as exc:
319 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100320
321 @staticmethod
tiernof27c79b2018-03-12 17:08:42 +0100322 def _format_out(data, session=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100323 """
324 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100325 :param data: response to be sent. Can be a dict, text or file
tiernoc94c3df2018-02-09 15:38:54 +0100326 :param session:
tiernof27c79b2018-03-12 17:08:42 +0100327 :param _format: The format to be set as Content-Type ir data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100328 :return: None
329 """
tierno0f98af52018-03-19 10:28:22 +0100330 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100331 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100332 if accept and "text/html" in accept:
333 return html.format(data, cherrypy.request, cherrypy.response, session)
tierno09c073e2018-04-26 13:36:48 +0200334 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100335 return
336 elif hasattr(data, "read"): # file object
337 if _format:
338 cherrypy.response.headers["Content-Type"] = _format
339 elif "b" in data.mode: # binariy asssumig zip
340 cherrypy.response.headers["Content-Type"] = 'application/zip'
341 else:
342 cherrypy.response.headers["Content-Type"] = 'text/plain'
343 # TODO check that cherrypy close file. If not implement pending things to close per thread next
344 return data
tierno0f98af52018-03-19 10:28:22 +0100345 if accept:
tiernoc94c3df2018-02-09 15:38:54 +0100346 if "application/json" in accept:
347 cherrypy.response.headers["Content-Type"] = 'application/json; charset=utf-8'
348 a = json.dumps(data, indent=4) + "\n"
349 return a.encode("utf8")
350 elif "text/html" in accept:
351 return html.format(data, cherrypy.request, cherrypy.response, session)
352
tiernof27c79b2018-03-12 17:08:42 +0100353 elif "application/yaml" in accept or "*/*" in accept or "text/plain" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100354 pass
355 else:
356 raise cherrypy.HTTPError(HTTPStatus.NOT_ACCEPTABLE.value,
357 "Only 'Accept' of type 'application/json' or 'application/yaml' "
358 "for output format are available")
359 cherrypy.response.headers["Content-Type"] = 'application/yaml'
360 return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False,
361 encoding='utf-8', allow_unicode=True) # , canonical=True, default_style='"'
362
363 @cherrypy.expose
364 def index(self, *args, **kwargs):
365 session = None
366 try:
367 if cherrypy.request.method == "GET":
Eduardo Sousa2f988212018-07-26 01:04:11 +0100368 session = self.authenticator.authorize()
tiernoc94c3df2018-02-09 15:38:54 +0100369 outdata = "Index page"
370 else:
371 raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value,
tierno2236d202018-05-16 19:05:16 +0200372 "Method {} not allowed for tokens".format(cherrypy.request.method))
tiernoc94c3df2018-02-09 15:38:54 +0100373
374 return self._format_out(outdata, session)
375
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100376 except (EngineException, AuthException) as e:
tiernoc94c3df2018-02-09 15:38:54 +0100377 cherrypy.log("index Exception {}".format(e))
378 cherrypy.response.status = e.http_code.value
379 return self._format_out("Welcome to OSM!", session)
380
381 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200382 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200383 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200384 global __version__, version_date
385 try:
386 if cherrypy.request.method != "GET":
387 raise NbiException("Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED)
388 elif args or kwargs:
389 raise NbiException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED)
390 return __version__ + " " + version_date
391 except NbiException as e:
392 cherrypy.response.status = e.http_code.value
393 problem_details = {
394 "code": e.http_code.name,
395 "status": e.http_code.value,
396 "detail": str(e),
397 }
398 return self._format_out(problem_details, None)
399
400 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100401 def token(self, method, token_id=None, kwargs=None):
tiernoc94c3df2018-02-09 15:38:54 +0100402 session = None
403 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +0100404 indata = self._format_in(kwargs)
405 if not isinstance(indata, dict):
406 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100407 try:
tiernoc94c3df2018-02-09 15:38:54 +0100408 if method == "GET":
Eduardo Sousa2f988212018-07-26 01:04:11 +0100409 session = self.authenticator.authorize()
tiernof27c79b2018-03-12 17:08:42 +0100410 if token_id:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100411 outdata = self.authenticator.get_token(session, token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100412 else:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100413 outdata = self.authenticator.get_token_list(session)
tiernoc94c3df2018-02-09 15:38:54 +0100414 elif method == "POST":
415 try:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100416 session = self.authenticator.authorize()
tiernoe1281182018-05-22 12:24:36 +0200417 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100418 session = None
419 if kwargs:
420 indata.update(kwargs)
Eduardo Sousa2f988212018-07-26 01:04:11 +0100421 outdata = self.authenticator.new_token(session, indata, cherrypy.request.remote)
tiernoc94c3df2018-02-09 15:38:54 +0100422 session = outdata
423 cherrypy.session['Authorization'] = outdata["_id"]
tierno0f98af52018-03-19 10:28:22 +0100424 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
tiernoc94c3df2018-02-09 15:38:54 +0100425 # cherrypy.response.cookie["Authorization"] = outdata["id"]
426 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
427 elif method == "DELETE":
tiernof27c79b2018-03-12 17:08:42 +0100428 if not token_id and "id" in kwargs:
tiernoc94c3df2018-02-09 15:38:54 +0100429 token_id = kwargs["id"]
tiernof27c79b2018-03-12 17:08:42 +0100430 elif not token_id:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100431 session = self.authenticator.authorize()
tiernoc94c3df2018-02-09 15:38:54 +0100432 token_id = session["_id"]
Eduardo Sousa2f988212018-07-26 01:04:11 +0100433 outdata = self.authenticator.del_token(token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100434 session = None
435 cherrypy.session['Authorization'] = "logout"
436 # cherrypy.response.cookie["Authorization"] = token_id
437 # cherrypy.response.cookie["Authorization"]['expires'] = 0
438 else:
439 raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
440 return self._format_out(outdata, session)
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100441 except (NbiException, EngineException, DbException, AuthException) as e:
tiernoc94c3df2018-02-09 15:38:54 +0100442 cherrypy.log("tokens Exception {}".format(e))
443 cherrypy.response.status = e.http_code.value
444 problem_details = {
445 "code": e.http_code.name,
446 "status": e.http_code.value,
447 "detail": str(e),
448 }
449 return self._format_out(problem_details, session)
450
451 @cherrypy.expose
452 def test(self, *args, **kwargs):
453 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +0100454 if args and args[0] == "help":
455 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nprune\nlogin\nlogin2\n"\
tiernoe1281182018-05-22 12:24:36 +0200456 "sleep/<time>\nmessage/topic\n</pre></html>"
tiernof27c79b2018-03-12 17:08:42 +0100457
458 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +0100459 try:
460 # self.engine.load_dbase(cherrypy.request.app.config)
461 self.engine.create_admin()
462 return "Done. User 'admin', password 'admin' created"
463 except Exception:
464 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
465 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +0100466 elif args and args[0] == "file":
467 return cherrypy.lib.static.serve_file(cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1],
468 "text/plain", "attachment")
469 elif args and args[0] == "file2":
470 f_path = cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1]
471 f = open(f_path, "r")
472 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100473 return f
tierno55945e72018-04-06 16:40:27 +0200474
tiernof27c79b2018-03-12 17:08:42 +0100475 elif len(args) == 2 and args[0] == "db-clear":
tiernocd54a4a2018-09-12 16:40:35 +0200476 return self.engine.del_item_list({"project_id": "admin", "admin": True}, args[1], kwargs)
tiernoc94c3df2018-02-09 15:38:54 +0100477 elif args and args[0] == "prune":
478 return self.engine.prune()
479 elif args and args[0] == "login":
480 if not cherrypy.request.headers.get("Authorization"):
481 cherrypy.response.headers["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
482 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
483 elif args and args[0] == "login2":
484 if not cherrypy.request.headers.get("Authorization"):
485 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
486 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
487 elif args and args[0] == "sleep":
488 sleep_time = 5
489 try:
490 sleep_time = int(args[1])
491 except Exception:
492 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
493 return self._format_out("Database already initialized")
494 thread_info = cherrypy.thread_data
495 print(thread_info)
496 time.sleep(sleep_time)
497 # thread_info
498 elif len(args) >= 2 and args[0] == "message":
499 topic = args[1]
tierno55945e72018-04-06 16:40:27 +0200500 return_text = "<html><pre>{} ->\n".format(topic)
tiernoc94c3df2018-02-09 15:38:54 +0100501 try:
tierno55945e72018-04-06 16:40:27 +0200502 if cherrypy.request.method == 'POST':
503 to_send = yaml.load(cherrypy.request.body)
504 for k, v in to_send.items():
505 self.engine.msg.write(topic, k, v)
506 return_text += " {}: {}\n".format(k, v)
507 elif cherrypy.request.method == 'GET':
508 for k, v in kwargs.items():
509 self.engine.msg.write(topic, k, yaml.load(v))
510 return_text += " {}: {}\n".format(k, yaml.load(v))
tiernoc94c3df2018-02-09 15:38:54 +0100511 except Exception as e:
tierno55945e72018-04-06 16:40:27 +0200512 return_text += "Error: " + str(e)
513 return_text += "</pre></html>\n"
514 return return_text
tiernoc94c3df2018-02-09 15:38:54 +0100515
516 return_text = (
517 "<html><pre>\nheaders:\n args: {}\n".format(args) +
518 " kwargs: {}\n".format(kwargs) +
519 " headers: {}\n".format(cherrypy.request.headers) +
520 " path_info: {}\n".format(cherrypy.request.path_info) +
521 " query_string: {}\n".format(cherrypy.request.query_string) +
522 " session: {}\n".format(cherrypy.session) +
523 " cookie: {}\n".format(cherrypy.request.cookie) +
524 " method: {}\n".format(cherrypy.request.method) +
525 " session: {}\n".format(cherrypy.session.get('fieldname')) +
526 " body:\n")
527 return_text += " length: {}\n".format(cherrypy.request.body.length)
528 if cherrypy.request.body.length:
529 return_text += " content: {}\n".format(
530 str(cherrypy.request.body.read(int(cherrypy.request.headers.get('Content-Length', 0)))))
531 if thread_info:
532 return_text += "thread: {}\n".format(thread_info)
533 return_text += "</pre></html>"
534 return return_text
535
tiernof27c79b2018-03-12 17:08:42 +0100536 def _check_valid_url_method(self, method, *args):
537 if len(args) < 3:
538 raise NbiException("URL must contain at least 'topic/version/item'", HTTPStatus.METHOD_NOT_ALLOWED)
539
540 reference = self.valid_methods
541 for arg in args:
542 if arg is None:
543 break
544 if not isinstance(reference, dict):
545 raise NbiException("URL contains unexpected extra items '{}'".format(arg),
546 HTTPStatus.METHOD_NOT_ALLOWED)
547
548 if arg in reference:
549 reference = reference[arg]
550 elif "<ID>" in reference:
551 reference = reference["<ID>"]
552 elif "*" in reference:
553 reference = reference["*"]
554 break
555 else:
556 raise NbiException("Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED)
557 if "TODO" in reference and method in reference["TODO"]:
558 raise NbiException("Method {} not supported yet for this URL".format(method), HTTPStatus.NOT_IMPLEMENTED)
tierno2236d202018-05-16 19:05:16 +0200559 elif "METHODS" in reference and method not in reference["METHODS"]:
tiernof27c79b2018-03-12 17:08:42 +0100560 raise NbiException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
561 return
562
563 @staticmethod
564 def _set_location_header(topic, version, item, id):
565 """
566 Insert response header Location with the URL of created item base on URL params
567 :param topic:
568 :param version:
569 :param item:
570 :param id:
571 :return: None
572 """
573 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
574 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(topic, version, item, id)
575 return
576
tiernoc94c3df2018-02-09 15:38:54 +0100577 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100578 def default(self, topic=None, version=None, item=None, _id=None, item2=None, *args, **kwargs):
tiernoc94c3df2018-02-09 15:38:54 +0100579 session = None
tiernof27c79b2018-03-12 17:08:42 +0100580 outdata = None
581 _format = None
tierno0f98af52018-03-19 10:28:22 +0100582 method = "DONE"
583 engine_item = None
tiernodb9dc582018-06-20 17:27:29 +0200584 rollback = []
585 session = None
tiernoc94c3df2018-02-09 15:38:54 +0100586 try:
tiernof27c79b2018-03-12 17:08:42 +0100587 if not topic or not version or not item:
588 raise NbiException("URL must contain at least 'topic/version/item'", HTTPStatus.METHOD_NOT_ALLOWED)
589 if topic not in ("admin", "vnfpkgm", "nsd", "nslcm"):
590 raise NbiException("URL topic '{}' not supported".format(topic), HTTPStatus.METHOD_NOT_ALLOWED)
tiernoc94c3df2018-02-09 15:38:54 +0100591 if version != 'v1':
592 raise NbiException("URL version '{}' not supported".format(version), HTTPStatus.METHOD_NOT_ALLOWED)
593
tiernof27c79b2018-03-12 17:08:42 +0100594 if kwargs and "METHOD" in kwargs and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
595 method = kwargs.pop("METHOD")
596 else:
597 method = cherrypy.request.method
tiernob92094f2018-05-11 13:44:22 +0200598 if kwargs and "FORCE" in kwargs:
599 force = kwargs.pop("FORCE")
600 else:
601 force = False
tiernof27c79b2018-03-12 17:08:42 +0100602
603 self._check_valid_url_method(method, topic, version, item, _id, item2, *args)
604
605 if topic == "admin" and item == "tokens":
606 return self.token(method, _id, kwargs)
607
tiernoc94c3df2018-02-09 15:38:54 +0100608 # self.engine.load_dbase(cherrypy.request.app.config)
Eduardo Sousa2f988212018-07-26 01:04:11 +0100609 session = self.authenticator.authorize()
tiernof27c79b2018-03-12 17:08:42 +0100610 indata = self._format_in(kwargs)
611 engine_item = item
612 if item == "subscriptions":
613 engine_item = topic + "_" + item
614 if item2:
615 engine_item = item2
tiernoc94c3df2018-02-09 15:38:54 +0100616
tiernof27c79b2018-03-12 17:08:42 +0100617 if topic == "nsd":
618 engine_item = "nsds"
619 elif topic == "vnfpkgm":
620 engine_item = "vnfds"
621 elif topic == "nslcm":
622 engine_item = "nsrs"
tierno65acb4d2018-04-06 16:42:40 +0200623 if item == "ns_lcm_op_occs":
624 engine_item = "nslcmops"
tiernof759d822018-06-11 18:54:54 +0200625 if item == "vnfrs" or item == "vnf_instances":
tierno0ffaa992018-05-09 13:21:56 +0200626 engine_item = "vnfrs"
tierno09c073e2018-04-26 13:36:48 +0200627 if engine_item == "vims": # TODO this is for backward compatibility, it will remove in the future
628 engine_item = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +0100629
630 if method == "GET":
tiernof27c79b2018-03-12 17:08:42 +0100631 if item2 in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd"):
632 if item2 in ("vnfd", "nsd"):
633 path = "$DESCRIPTOR"
634 elif args:
635 path = args
636 elif item2 == "artifacts":
637 path = ()
638 else:
639 path = None
640 file, _format = self.engine.get_file(session, engine_item, _id, path,
tierno2236d202018-05-16 19:05:16 +0200641 cherrypy.request.headers.get("Accept"))
tiernof27c79b2018-03-12 17:08:42 +0100642 outdata = file
643 elif not _id:
644 outdata = self.engine.get_item_list(session, engine_item, kwargs)
tiernoc94c3df2018-02-09 15:38:54 +0100645 else:
tiernof27c79b2018-03-12 17:08:42 +0100646 outdata = self.engine.get_item(session, engine_item, _id)
647 elif method == "POST":
648 if item in ("ns_descriptors_content", "vnf_packages_content"):
649 _id = cherrypy.request.headers.get("Transaction-Id")
650 if not _id:
tiernodb9dc582018-06-20 17:27:29 +0200651 _id = self.engine.new_item(rollback, session, engine_item, {}, None, cherrypy.request.headers,
tiernob92094f2018-05-11 13:44:22 +0200652 force=force)
tierno2236d202018-05-16 19:05:16 +0200653 completed = self.engine.upload_content(session, engine_item, _id, indata, kwargs,
654 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +0100655 if completed:
656 self._set_location_header(topic, version, item, _id)
657 else:
658 cherrypy.response.headers["Transaction-Id"] = _id
659 outdata = {"id": _id}
tierno65acb4d2018-04-06 16:42:40 +0200660 elif item == "ns_instances_content":
tiernodb9dc582018-06-20 17:27:29 +0200661 _id = self.engine.new_item(rollback, session, engine_item, indata, kwargs, force=force)
tierno0da52252018-06-27 15:47:22 +0200662 self.engine.ns_operation(rollback, session, _id, "instantiate", indata, None)
tiernof27c79b2018-03-12 17:08:42 +0100663 self._set_location_header(topic, version, item, _id)
tiernof27c79b2018-03-12 17:08:42 +0100664 outdata = {"id": _id}
tierno65acb4d2018-04-06 16:42:40 +0200665 elif item == "ns_instances" and item2:
tiernodb9dc582018-06-20 17:27:29 +0200666 _id = self.engine.ns_operation(rollback, session, _id, item2, indata, kwargs)
tierno65acb4d2018-04-06 16:42:40 +0200667 self._set_location_header(topic, version, "ns_lcm_op_occs", _id)
668 outdata = {"id": _id}
669 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +0100670 else:
tiernodb9dc582018-06-20 17:27:29 +0200671 _id = self.engine.new_item(rollback, session, engine_item, indata, kwargs, cherrypy.request.headers,
tiernob92094f2018-05-11 13:44:22 +0200672 force=force)
tiernof27c79b2018-03-12 17:08:42 +0100673 self._set_location_header(topic, version, item, _id)
674 outdata = {"id": _id}
tierno65acb4d2018-04-06 16:42:40 +0200675 # TODO form NsdInfo when item in ("ns_descriptors", "vnf_packages")
tiernof27c79b2018-03-12 17:08:42 +0100676 cherrypy.response.status = HTTPStatus.CREATED.value
tierno09c073e2018-04-26 13:36:48 +0200677
tiernoc94c3df2018-02-09 15:38:54 +0100678 elif method == "DELETE":
679 if not _id:
tiernof27c79b2018-03-12 17:08:42 +0100680 outdata = self.engine.del_item_list(session, engine_item, kwargs)
tierno09c073e2018-04-26 13:36:48 +0200681 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +0100682 else: # len(args) > 1
tiernob62b7ac2018-05-28 16:50:20 +0200683 if item == "ns_instances_content" and not force:
tiernodb9dc582018-06-20 17:27:29 +0200684 opp_id = self.engine.ns_operation(rollback, session, _id, "terminate", {"autoremove": True},
685 None)
tierno3ace63c2018-05-03 17:51:43 +0200686 outdata = {"_id": opp_id}
tierno09c073e2018-04-26 13:36:48 +0200687 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tierno65acb4d2018-04-06 16:42:40 +0200688 else:
tierno65acb4d2018-04-06 16:42:40 +0200689 self.engine.del_item(session, engine_item, _id, force)
tierno09c073e2018-04-26 13:36:48 +0200690 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
691 if engine_item in ("vim_accounts", "sdns"):
692 cherrypy.response.status = HTTPStatus.ACCEPTED.value
693
tierno7ae10112018-05-18 14:36:02 +0200694 elif method in ("PUT", "PATCH"):
tiernocd54a4a2018-09-12 16:40:35 +0200695 outdata = None
tiernof27c79b2018-03-12 17:08:42 +0100696 if not indata and not kwargs:
tiernoc94c3df2018-02-09 15:38:54 +0100697 raise NbiException("Nothing to update. Provide payload and/or query string",
698 HTTPStatus.BAD_REQUEST)
tierno7ae10112018-05-18 14:36:02 +0200699 if item2 in ("nsd_content", "package_content") and method == "PUT":
tierno2236d202018-05-16 19:05:16 +0200700 completed = self.engine.upload_content(session, engine_item, _id, indata, kwargs,
701 cherrypy.request.headers)
tiernof27c79b2018-03-12 17:08:42 +0100702 if not completed:
703 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +0100704 else:
tiernocd54a4a2018-09-12 16:40:35 +0200705 self.engine.edit_item(session, engine_item, _id, indata, kwargs, force=force)
706 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernoc94c3df2018-02-09 15:38:54 +0100707 else:
708 raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tiernof27c79b2018-03-12 17:08:42 +0100709 return self._format_out(outdata, session, _format)
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100710 except (NbiException, EngineException, DbException, FsException, MsgException, AuthException) as e:
tiernoc94c3df2018-02-09 15:38:54 +0100711 cherrypy.log("Exception {}".format(e))
712 cherrypy.response.status = e.http_code.value
tierno3ace63c2018-05-03 17:51:43 +0200713 if hasattr(outdata, "close"): # is an open file
714 outdata.close()
tiernodb9dc582018-06-20 17:27:29 +0200715 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +0200716 try:
tiernodb9dc582018-06-20 17:27:29 +0200717 self.engine.del_item(**rollback_item, session=session, force=True)
tierno3ace63c2018-05-03 17:51:43 +0200718 except Exception as e2:
tiernodb9dc582018-06-20 17:27:29 +0200719 cherrypy.log("Rollback Exception {}: {}".format(rollback_item, e2))
tierno0f98af52018-03-19 10:28:22 +0100720 error_text = str(e)
721 if isinstance(e, MsgException):
722 error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
723 engine_item[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +0100724 problem_details = {
725 "code": e.http_code.name,
726 "status": e.http_code.value,
727 "detail": str(e),
728 }
729 return self._format_out(problem_details, session)
730 # raise cherrypy.HTTPError(e.http_code.value, str(e))
731
732
733# def validate_password(realm, username, password):
734# cherrypy.log("realm "+ str(realm))
735# if username == "admin" and password == "admin":
736# return True
737# return False
738
739
740def _start_service():
741 """
742 Callback function called when cherrypy.engine starts
743 Override configuration with env variables
744 Set database, storage, message configuration
745 Init database with admin/admin user password
746 """
747 cherrypy.log.error("Starting osm_nbi")
748 # update general cherrypy configuration
749 update_dict = {}
750
751 engine_config = cherrypy.tree.apps['/osm'].config
752 for k, v in environ.items():
753 if not k.startswith("OSMNBI_"):
754 continue
tiernoe1281182018-05-22 12:24:36 +0200755 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +0100756 if not k2:
757 continue
758 try:
759 # update static configuration
760 if k == 'OSMNBI_STATIC_DIR':
761 engine_config["/static"]['tools.staticdir.dir'] = v
762 engine_config["/static"]['tools.staticdir.on'] = True
763 elif k == 'OSMNBI_SOCKET_PORT' or k == 'OSMNBI_SERVER_PORT':
764 update_dict['server.socket_port'] = int(v)
765 elif k == 'OSMNBI_SOCKET_HOST' or k == 'OSMNBI_SERVER_HOST':
766 update_dict['server.socket_host'] = v
tiernof5298be2018-05-16 14:43:57 +0200767 elif k1 in ("server", "test", "auth", "log"):
768 update_dict[k1 + '.' + k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100769 elif k1 in ("message", "database", "storage", "authentication"):
tiernof5298be2018-05-16 14:43:57 +0200770 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100771 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +0100772 engine_config[k1][k2] = int(v)
773 else:
774 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100775
tiernoc94c3df2018-02-09 15:38:54 +0100776 except ValueError as e:
777 cherrypy.log.error("Ignoring environ '{}': " + str(e))
778 except Exception as e:
779 cherrypy.log.warn("skipping environ '{}' on exception '{}'".format(k, e))
780
781 if update_dict:
782 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +0200783 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +0100784
785 # logging cherrypy
786 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
787 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
788 logger_server = logging.getLogger("cherrypy.error")
789 logger_access = logging.getLogger("cherrypy.access")
790 logger_cherry = logging.getLogger("cherrypy")
791 logger_nbi = logging.getLogger("nbi")
792
tiernof5298be2018-05-16 14:43:57 +0200793 if "log.file" in engine_config["global"]:
794 file_handler = logging.handlers.RotatingFileHandler(engine_config["global"]["log.file"],
tiernoc94c3df2018-02-09 15:38:54 +0100795 maxBytes=100e6, backupCount=9, delay=0)
796 file_handler.setFormatter(log_formatter_simple)
797 logger_cherry.addHandler(file_handler)
798 logger_nbi.addHandler(file_handler)
799 else:
800 for format_, logger in {"nbi.server": logger_server,
801 "nbi.access": logger_access,
802 "%(name)s %(filename)s:%(lineno)s": logger_nbi
803 }.items():
804 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
805 log_formatter_cherry = logging.Formatter(log_format_cherry, datefmt='%Y-%m-%dT%H:%M:%S')
806 str_handler = logging.StreamHandler()
807 str_handler.setFormatter(log_formatter_cherry)
808 logger.addHandler(str_handler)
809
tiernof5298be2018-05-16 14:43:57 +0200810 if engine_config["global"].get("log.level"):
811 logger_cherry.setLevel(engine_config["global"]["log.level"])
812 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +0100813
814 # logging other modules
815 for k1, logname in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
816 engine_config[k1]["logger_name"] = logname
817 logger_module = logging.getLogger(logname)
818 if "logfile" in engine_config[k1]:
819 file_handler = logging.handlers.RotatingFileHandler(engine_config[k1]["logfile"],
tierno2236d202018-05-16 19:05:16 +0200820 maxBytes=100e6, backupCount=9, delay=0)
tiernoc94c3df2018-02-09 15:38:54 +0100821 file_handler.setFormatter(log_formatter_simple)
822 logger_module.addHandler(file_handler)
823 if "loglevel" in engine_config[k1]:
824 logger_module.setLevel(engine_config[k1]["loglevel"])
825 # TODO add more entries, e.g.: storage
826 cherrypy.tree.apps['/osm'].root.engine.start(engine_config)
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100827 cherrypy.tree.apps['/osm'].root.authenticator.start(engine_config)
tiernoc94c3df2018-02-09 15:38:54 +0100828 try:
tierno4a946e42018-04-12 17:48:49 +0200829 cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version)
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100830 cherrypy.tree.apps['/osm'].root.authenticator.init_db(target_version=auth_database_version)
831 except (EngineException, AuthException):
tiernoc94c3df2018-02-09 15:38:54 +0100832 pass
833 # getenv('OSMOPENMANO_TENANT', None)
834
835
836def _stop_service():
837 """
838 Callback function called when cherrypy.engine stops
839 TODO: Ending database connections.
840 """
841 cherrypy.tree.apps['/osm'].root.engine.stop()
842 cherrypy.log.error("Stopping osm_nbi")
843
tierno2236d202018-05-16 19:05:16 +0200844
tiernof5298be2018-05-16 14:43:57 +0200845def nbi(config_file):
tiernoc94c3df2018-02-09 15:38:54 +0100846 # conf = {
847 # '/': {
848 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
849 # 'tools.sessions.on': True,
850 # 'tools.response_headers.on': True,
851 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
852 # }
853 # }
854 # cherrypy.Server.ssl_module = 'builtin'
855 # cherrypy.Server.ssl_certificate = "http/cert.pem"
856 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
857 # cherrypy.Server.thread_pool = 10
858 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
859
860 # cherrypy.config.update({'tools.auth_basic.on': True,
861 # 'tools.auth_basic.realm': 'localhost',
862 # 'tools.auth_basic.checkpassword': validate_password})
863 cherrypy.engine.subscribe('start', _start_service)
864 cherrypy.engine.subscribe('stop', _stop_service)
tiernof5298be2018-05-16 14:43:57 +0200865 cherrypy.quickstart(Server(), '/osm', config_file)
866
867
868def usage():
869 print("""Usage: {} [options]
870 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
871 -h|--help: shows this help
872 """.format(sys.argv[0]))
tierno2236d202018-05-16 19:05:16 +0200873 # --log-socket-host HOST: send logs to this host")
874 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +0100875
876
877if __name__ == '__main__':
tiernof5298be2018-05-16 14:43:57 +0200878 try:
879 # load parameters and configuration
880 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
881 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
882 config_file = None
883 for o, a in opts:
884 if o in ("-h", "--help"):
885 usage()
886 sys.exit()
887 elif o in ("-c", "--config"):
888 config_file = a
889 # elif o == "--log-socket-port":
890 # log_socket_port = a
891 # elif o == "--log-socket-host":
892 # log_socket_host = a
893 # elif o == "--log-file":
894 # log_file = a
895 else:
896 assert False, "Unhandled option"
897 if config_file:
898 if not path.isfile(config_file):
899 print("configuration file '{}' that not exist".format(config_file), file=sys.stderr)
900 exit(1)
901 else:
902 for config_file in (__file__[:__file__.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
903 if path.isfile(config_file):
904 break
905 else:
906 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys.stderr)
907 exit(1)
908 nbi(config_file)
909 except getopt.GetoptError as e:
910 print(str(e), file=sys.stderr)
911 # usage()
912 exit(1)