blob: 1d53396f05321122f1400c26f5b772b1b4247f6e [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
tiernocb83c942018-09-24 17:28:13 +020078 /pdu/v1
79 /pdu_descriptor O O
80 /<id> O O O O
tiernof27c79b2018-03-12 17:08:42 +010081 /admin/v1
82 /tokens O O
tierno2236d202018-05-16 19:05:16 +020083 /<id> O O
tiernof27c79b2018-03-12 17:08:42 +010084 /users O O
tiernocd54a4a2018-09-12 16:40:35 +020085 /<id> O O O O
tiernof27c79b2018-03-12 17:08:42 +010086 /projects O O
tierno2236d202018-05-16 19:05:16 +020087 /<id> O O
tierno09c073e2018-04-26 13:36:48 +020088 /vims_accounts (also vims for compatibility) O O
tierno2236d202018-05-16 19:05:16 +020089 /<id> O O O
tierno0f98af52018-03-19 10:28:22 +010090 /sdns O O
tierno2236d202018-05-16 19:05:16 +020091 /<id> O O O
tiernoc94c3df2018-02-09 15:38:54 +010092
tierno2236d202018-05-16 19:05:16 +020093query string:
94 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
95 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
96 item of the array, that is, pass if any item of the array pass the filter.
97 It allows both ne and neq for not equal
98 TODO: 4.3.3 Attribute selectors
99 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
tiernoc94c3df2018-02-09 15:38:54 +0100100 (none) … same as “exclude_default”
101 all_fields … all attributes.
tierno2236d202018-05-16 19:05:16 +0200102 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
103 conditionally mandatory, and that are not provided in <list>.
104 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
105 are not conditionally mandatory, and that are provided in <list>.
106 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
107 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
108 the particular resource
109 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
110 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
111 present specification for the particular resource, but that are not part of <list>
tiernoc94c3df2018-02-09 15:38:54 +0100112Header field name Reference Example Descriptions
113 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
114 This header field shall be present if the response is expected to have a non-empty message body.
115 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
116 This header field shall be present if the request has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200117 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
118 Details are specified in clause 4.5.3.
tiernoc94c3df2018-02-09 15:38:54 +0100119 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
120Header field name Reference Example Descriptions
121 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
122 This header field shall be present if the response has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200123 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
124 new resource has been created.
tiernoc94c3df2018-02-09 15:38:54 +0100125 This header field shall be present if the response status code is 201 or 3xx.
tierno2236d202018-05-16 19:05:16 +0200126 In the present document this header field is also used if the response status code is 202 and a new resource was
127 created.
128 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
129 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
130 token.
131 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
132 certain resources.
133 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
134 response, and the total length of the file.
tiernoc94c3df2018-02-09 15:38:54 +0100135 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
tiernoc94c3df2018-02-09 15:38:54 +0100136"""
137
138
139class NbiException(Exception):
140
141 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
142 Exception.__init__(self, message)
143 self.http_code = http_code
144
145
146class Server(object):
147 instance = 0
148 # to decode bytes to str
149 reader = getreader("utf-8")
150
151 def __init__(self):
152 self.instance += 1
153 self.engine = Engine()
Eduardo Sousad1b525d2018-10-04 04:24:18 +0100154 self.authenticator = Authenticator()
tiernof27c79b2018-03-12 17:08:42 +0100155 self.valid_methods = { # contains allowed URL and methods
156 "admin": {
157 "v1": {
tierno0f98af52018-03-19 10:28:22 +0100158 "tokens": {"METHODS": ("GET", "POST", "DELETE"),
tierno2236d202018-05-16 19:05:16 +0200159 "<ID>": {"METHODS": ("GET", "DELETE")}
160 },
tierno0f98af52018-03-19 10:28:22 +0100161 "users": {"METHODS": ("GET", "POST"),
tiernocd54a4a2018-09-12 16:40:35 +0200162 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200163 },
tierno0f98af52018-03-19 10:28:22 +0100164 "projects": {"METHODS": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200165 "<ID>": {"METHODS": ("GET", "DELETE")}
166 },
tierno0f98af52018-03-19 10:28:22 +0100167 "vims": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200168 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200169 },
tierno09c073e2018-04-26 13:36:48 +0200170 "vim_accounts": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200171 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200172 },
tierno0f98af52018-03-19 10:28:22 +0100173 "sdns": {"METHODS": ("GET", "POST"),
tierno7ae10112018-05-18 14:36:02 +0200174 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
tierno2236d202018-05-16 19:05:16 +0200175 },
tiernof27c79b2018-03-12 17:08:42 +0100176 }
177 },
tiernocb83c942018-09-24 17:28:13 +0200178 "pdu": {
179 "v1": {
180 "pdu_descriptors": {"METHODS": ("GET", "POST"),
181 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT")}
182 },
183 }
184 },
tiernof27c79b2018-03-12 17:08:42 +0100185 "nsd": {
186 "v1": {
tierno2236d202018-05-16 19:05:16 +0200187 "ns_descriptors_content": {"METHODS": ("GET", "POST"),
188 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
189 },
190 "ns_descriptors": {"METHODS": ("GET", "POST"),
191 "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH",
192 "nsd_content": {"METHODS": ("GET", "PUT")},
193 "nsd": {"METHODS": "GET"}, # descriptor inside package
194 "artifacts": {"*": {"METHODS": "GET"}}
195 }
196 },
tiernof27c79b2018-03-12 17:08:42 +0100197 "pnf_descriptors": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200198 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
199 "pnfd_content": {"TODO": ("GET", "PUT")}
200 }
201 },
tiernof27c79b2018-03-12 17:08:42 +0100202 "subscriptions": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200203 "<ID>": {"TODO": ("GET", "DELETE")}
204 },
tiernof27c79b2018-03-12 17:08:42 +0100205 }
206 },
207 "vnfpkgm": {
208 "v1": {
tierno2236d202018-05-16 19:05:16 +0200209 "vnf_packages_content": {"METHODS": ("GET", "POST"),
210 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
211 },
212 "vnf_packages": {"METHODS": ("GET", "POST"),
tiernoaae4dc42018-06-11 18:55:50 +0200213 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
tierno2236d202018-05-16 19:05:16 +0200214 "package_content": {"METHODS": ("GET", "PUT"), # package
215 "upload_from_uri": {"TODO": "POST"}
216 },
217 "vnfd": {"METHODS": "GET"}, # descriptor inside package
218 "artifacts": {"*": {"METHODS": "GET"}}
219 }
220 },
tiernof27c79b2018-03-12 17:08:42 +0100221 "subscriptions": {"TODO": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200222 "<ID>": {"TODO": ("GET", "DELETE")}
223 },
tiernof27c79b2018-03-12 17:08:42 +0100224 }
225 },
226 "nslcm": {
227 "v1": {
228 "ns_instances_content": {"METHODS": ("GET", "POST"),
tierno2236d202018-05-16 19:05:16 +0200229 "<ID>": {"METHODS": ("GET", "DELETE")}
230 },
tierno65acb4d2018-04-06 16:42:40 +0200231 "ns_instances": {"METHODS": ("GET", "POST"),
tierno95692442018-05-24 18:05:28 +0200232 "<ID>": {"METHODS": ("GET", "DELETE"),
tiernof759d822018-06-11 18:54:54 +0200233 "scale": {"METHODS": "POST"},
tierno2236d202018-05-16 19:05:16 +0200234 "terminate": {"METHODS": "POST"},
235 "instantiate": {"METHODS": "POST"},
236 "action": {"METHODS": "POST"},
237 }
238 },
tierno65acb4d2018-04-06 16:42:40 +0200239 "ns_lcm_op_occs": {"METHODS": "GET",
tierno2236d202018-05-16 19:05:16 +0200240 "<ID>": {"METHODS": "GET"},
241 },
tierno0ffaa992018-05-09 13:21:56 +0200242 "vnfrs": {"METHODS": ("GET"),
tierno2236d202018-05-16 19:05:16 +0200243 "<ID>": {"METHODS": ("GET")}
244 },
tiernof759d822018-06-11 18:54:54 +0200245 "vnf_instances": {"METHODS": ("GET"),
246 "<ID>": {"METHODS": ("GET")}
247 },
tiernof27c79b2018-03-12 17:08:42 +0100248 }
249 },
250 }
tiernoc94c3df2018-02-09 15:38:54 +0100251
tiernoc94c3df2018-02-09 15:38:54 +0100252 def _format_in(self, kwargs):
253 try:
254 indata = None
255 if cherrypy.request.body.length:
256 error_text = "Invalid input format "
257
258 if "Content-Type" in cherrypy.request.headers:
259 if "application/json" in cherrypy.request.headers["Content-Type"]:
260 error_text = "Invalid json format "
261 indata = json.load(self.reader(cherrypy.request.body))
gcalvinode4adfe2018-10-30 11:46:09 +0100262 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100263 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
264 error_text = "Invalid yaml format "
265 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100266 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100267 elif "application/binary" in cherrypy.request.headers["Content-Type"] or \
268 "application/gzip" in cherrypy.request.headers["Content-Type"] or \
tiernof27c79b2018-03-12 17:08:42 +0100269 "application/zip" in cherrypy.request.headers["Content-Type"] or \
270 "text/plain" in cherrypy.request.headers["Content-Type"]:
271 indata = cherrypy.request.body # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100272 elif "multipart/form-data" in cherrypy.request.headers["Content-Type"]:
273 if "descriptor_file" in kwargs:
274 filecontent = kwargs.pop("descriptor_file")
275 if not filecontent.file:
276 raise NbiException("empty file or content", HTTPStatus.BAD_REQUEST)
tiernof27c79b2018-03-12 17:08:42 +0100277 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100278 if filecontent.content_type.value:
279 cherrypy.request.headers["Content-Type"] = filecontent.content_type.value
280 else:
281 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
282 # "Only 'Content-Type' of type 'application/json' or
283 # 'application/yaml' for input format are available")
284 error_text = "Invalid yaml format "
285 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100286 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100287 else:
288 error_text = "Invalid yaml format "
289 indata = yaml.load(cherrypy.request.body)
gcalvinode4adfe2018-10-30 11:46:09 +0100290 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100291 if not indata:
292 indata = {}
293
tiernoc94c3df2018-02-09 15:38:54 +0100294 format_yaml = False
295 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
296 format_yaml = True
297
298 for k, v in kwargs.items():
299 if isinstance(v, str):
300 if v == "":
301 kwargs[k] = None
302 elif format_yaml:
303 try:
304 kwargs[k] = yaml.load(v)
tiernoe1281182018-05-22 12:24:36 +0200305 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100306 pass
307 elif k.endswith(".gt") or k.endswith(".lt") or k.endswith(".gte") or k.endswith(".lte"):
308 try:
309 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200310 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100311 try:
312 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200313 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100314 pass
315 elif v.find(",") > 0:
316 kwargs[k] = v.split(",")
317 elif isinstance(v, (list, tuple)):
318 for index in range(0, len(v)):
319 if v[index] == "":
320 v[index] = None
321 elif format_yaml:
322 try:
323 v[index] = yaml.load(v[index])
tiernoe1281182018-05-22 12:24:36 +0200324 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100325 pass
326
tiernof27c79b2018-03-12 17:08:42 +0100327 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100328 except (ValueError, yaml.YAMLError) as exc:
329 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
330 except KeyError as exc:
331 raise NbiException("Query string error: " + str(exc), HTTPStatus.BAD_REQUEST)
tiernob92094f2018-05-11 13:44:22 +0200332 except Exception as exc:
333 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100334
335 @staticmethod
tiernof27c79b2018-03-12 17:08:42 +0100336 def _format_out(data, session=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100337 """
338 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100339 :param data: response to be sent. Can be a dict, text or file
tiernoc94c3df2018-02-09 15:38:54 +0100340 :param session:
tiernof27c79b2018-03-12 17:08:42 +0100341 :param _format: The format to be set as Content-Type ir data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100342 :return: None
343 """
tierno0f98af52018-03-19 10:28:22 +0100344 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100345 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100346 if accept and "text/html" in accept:
347 return html.format(data, cherrypy.request, cherrypy.response, session)
tierno09c073e2018-04-26 13:36:48 +0200348 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100349 return
350 elif hasattr(data, "read"): # file object
351 if _format:
352 cherrypy.response.headers["Content-Type"] = _format
353 elif "b" in data.mode: # binariy asssumig zip
354 cherrypy.response.headers["Content-Type"] = 'application/zip'
355 else:
356 cherrypy.response.headers["Content-Type"] = 'text/plain'
357 # TODO check that cherrypy close file. If not implement pending things to close per thread next
358 return data
tierno0f98af52018-03-19 10:28:22 +0100359 if accept:
tiernoc94c3df2018-02-09 15:38:54 +0100360 if "application/json" in accept:
361 cherrypy.response.headers["Content-Type"] = 'application/json; charset=utf-8'
362 a = json.dumps(data, indent=4) + "\n"
363 return a.encode("utf8")
364 elif "text/html" in accept:
365 return html.format(data, cherrypy.request, cherrypy.response, session)
366
tiernof27c79b2018-03-12 17:08:42 +0100367 elif "application/yaml" in accept or "*/*" in accept or "text/plain" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100368 pass
369 else:
370 raise cherrypy.HTTPError(HTTPStatus.NOT_ACCEPTABLE.value,
371 "Only 'Accept' of type 'application/json' or 'application/yaml' "
372 "for output format are available")
373 cherrypy.response.headers["Content-Type"] = 'application/yaml'
374 return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False,
375 encoding='utf-8', allow_unicode=True) # , canonical=True, default_style='"'
376
377 @cherrypy.expose
378 def index(self, *args, **kwargs):
379 session = None
380 try:
381 if cherrypy.request.method == "GET":
Eduardo Sousa2f988212018-07-26 01:04:11 +0100382 session = self.authenticator.authorize()
tiernoc94c3df2018-02-09 15:38:54 +0100383 outdata = "Index page"
384 else:
385 raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value,
tierno2236d202018-05-16 19:05:16 +0200386 "Method {} not allowed for tokens".format(cherrypy.request.method))
tiernoc94c3df2018-02-09 15:38:54 +0100387
388 return self._format_out(outdata, session)
389
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100390 except (EngineException, AuthException) as e:
tiernoc94c3df2018-02-09 15:38:54 +0100391 cherrypy.log("index Exception {}".format(e))
392 cherrypy.response.status = e.http_code.value
393 return self._format_out("Welcome to OSM!", session)
394
395 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200396 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200397 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200398 global __version__, version_date
399 try:
400 if cherrypy.request.method != "GET":
401 raise NbiException("Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED)
402 elif args or kwargs:
403 raise NbiException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED)
404 return __version__ + " " + version_date
405 except NbiException as e:
406 cherrypy.response.status = e.http_code.value
407 problem_details = {
408 "code": e.http_code.name,
409 "status": e.http_code.value,
410 "detail": str(e),
411 }
412 return self._format_out(problem_details, None)
413
414 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100415 def token(self, method, token_id=None, kwargs=None):
tiernoc94c3df2018-02-09 15:38:54 +0100416 session = None
417 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +0100418 indata = self._format_in(kwargs)
419 if not isinstance(indata, dict):
420 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100421 try:
tiernoc94c3df2018-02-09 15:38:54 +0100422 if method == "GET":
Eduardo Sousa2f988212018-07-26 01:04:11 +0100423 session = self.authenticator.authorize()
tiernof27c79b2018-03-12 17:08:42 +0100424 if token_id:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100425 outdata = self.authenticator.get_token(session, token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100426 else:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100427 outdata = self.authenticator.get_token_list(session)
tiernoc94c3df2018-02-09 15:38:54 +0100428 elif method == "POST":
429 try:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100430 session = self.authenticator.authorize()
tiernoe1281182018-05-22 12:24:36 +0200431 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100432 session = None
433 if kwargs:
434 indata.update(kwargs)
Eduardo Sousa2f988212018-07-26 01:04:11 +0100435 outdata = self.authenticator.new_token(session, indata, cherrypy.request.remote)
tiernoc94c3df2018-02-09 15:38:54 +0100436 session = outdata
437 cherrypy.session['Authorization'] = outdata["_id"]
tierno0f98af52018-03-19 10:28:22 +0100438 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
tiernoc94c3df2018-02-09 15:38:54 +0100439 # cherrypy.response.cookie["Authorization"] = outdata["id"]
440 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
441 elif method == "DELETE":
tiernof27c79b2018-03-12 17:08:42 +0100442 if not token_id and "id" in kwargs:
tiernoc94c3df2018-02-09 15:38:54 +0100443 token_id = kwargs["id"]
tiernof27c79b2018-03-12 17:08:42 +0100444 elif not token_id:
Eduardo Sousa2f988212018-07-26 01:04:11 +0100445 session = self.authenticator.authorize()
tiernoc94c3df2018-02-09 15:38:54 +0100446 token_id = session["_id"]
Eduardo Sousa2f988212018-07-26 01:04:11 +0100447 outdata = self.authenticator.del_token(token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100448 session = None
449 cherrypy.session['Authorization'] = "logout"
450 # cherrypy.response.cookie["Authorization"] = token_id
451 # cherrypy.response.cookie["Authorization"]['expires'] = 0
452 else:
453 raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
454 return self._format_out(outdata, session)
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100455 except (NbiException, EngineException, DbException, AuthException) as e:
tiernoc94c3df2018-02-09 15:38:54 +0100456 cherrypy.log("tokens Exception {}".format(e))
457 cherrypy.response.status = e.http_code.value
458 problem_details = {
459 "code": e.http_code.name,
460 "status": e.http_code.value,
461 "detail": str(e),
462 }
463 return self._format_out(problem_details, session)
464
465 @cherrypy.expose
466 def test(self, *args, **kwargs):
467 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +0100468 if args and args[0] == "help":
469 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nprune\nlogin\nlogin2\n"\
tiernoe1281182018-05-22 12:24:36 +0200470 "sleep/<time>\nmessage/topic\n</pre></html>"
tiernof27c79b2018-03-12 17:08:42 +0100471
472 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +0100473 try:
474 # self.engine.load_dbase(cherrypy.request.app.config)
475 self.engine.create_admin()
476 return "Done. User 'admin', password 'admin' created"
477 except Exception:
478 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
479 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +0100480 elif args and args[0] == "file":
481 return cherrypy.lib.static.serve_file(cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1],
482 "text/plain", "attachment")
483 elif args and args[0] == "file2":
484 f_path = cherrypy.tree.apps['/osm'].config["storage"]["path"] + "/" + args[1]
485 f = open(f_path, "r")
486 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100487 return f
tierno55945e72018-04-06 16:40:27 +0200488
tiernof27c79b2018-03-12 17:08:42 +0100489 elif len(args) == 2 and args[0] == "db-clear":
tiernob24258a2018-10-04 18:39:49 +0200490 return self.engine.db.del_list(args[1], kwargs)
tiernoc94c3df2018-02-09 15:38:54 +0100491 elif args and args[0] == "prune":
492 return self.engine.prune()
493 elif args and args[0] == "login":
494 if not cherrypy.request.headers.get("Authorization"):
495 cherrypy.response.headers["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
496 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
497 elif args and args[0] == "login2":
498 if not cherrypy.request.headers.get("Authorization"):
499 cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
500 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
501 elif args and args[0] == "sleep":
502 sleep_time = 5
503 try:
504 sleep_time = int(args[1])
505 except Exception:
506 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
507 return self._format_out("Database already initialized")
508 thread_info = cherrypy.thread_data
509 print(thread_info)
510 time.sleep(sleep_time)
511 # thread_info
512 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +0200513 main_topic = args[1]
514 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +0100515 try:
tierno55945e72018-04-06 16:40:27 +0200516 if cherrypy.request.method == 'POST':
517 to_send = yaml.load(cherrypy.request.body)
518 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +0200519 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +0200520 return_text += " {}: {}\n".format(k, v)
521 elif cherrypy.request.method == 'GET':
522 for k, v in kwargs.items():
tiernob24258a2018-10-04 18:39:49 +0200523 self.engine.msg.write(main_topic, k, yaml.load(v))
tierno55945e72018-04-06 16:40:27 +0200524 return_text += " {}: {}\n".format(k, yaml.load(v))
tiernoc94c3df2018-02-09 15:38:54 +0100525 except Exception as e:
tierno55945e72018-04-06 16:40:27 +0200526 return_text += "Error: " + str(e)
527 return_text += "</pre></html>\n"
528 return return_text
tiernoc94c3df2018-02-09 15:38:54 +0100529
530 return_text = (
531 "<html><pre>\nheaders:\n args: {}\n".format(args) +
532 " kwargs: {}\n".format(kwargs) +
533 " headers: {}\n".format(cherrypy.request.headers) +
534 " path_info: {}\n".format(cherrypy.request.path_info) +
535 " query_string: {}\n".format(cherrypy.request.query_string) +
536 " session: {}\n".format(cherrypy.session) +
537 " cookie: {}\n".format(cherrypy.request.cookie) +
538 " method: {}\n".format(cherrypy.request.method) +
539 " session: {}\n".format(cherrypy.session.get('fieldname')) +
540 " body:\n")
541 return_text += " length: {}\n".format(cherrypy.request.body.length)
542 if cherrypy.request.body.length:
543 return_text += " content: {}\n".format(
544 str(cherrypy.request.body.read(int(cherrypy.request.headers.get('Content-Length', 0)))))
545 if thread_info:
546 return_text += "thread: {}\n".format(thread_info)
547 return_text += "</pre></html>"
548 return return_text
549
tiernof27c79b2018-03-12 17:08:42 +0100550 def _check_valid_url_method(self, method, *args):
551 if len(args) < 3:
tiernob24258a2018-10-04 18:39:49 +0200552 raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus.METHOD_NOT_ALLOWED)
tiernof27c79b2018-03-12 17:08:42 +0100553
554 reference = self.valid_methods
555 for arg in args:
556 if arg is None:
557 break
558 if not isinstance(reference, dict):
559 raise NbiException("URL contains unexpected extra items '{}'".format(arg),
560 HTTPStatus.METHOD_NOT_ALLOWED)
561
562 if arg in reference:
563 reference = reference[arg]
564 elif "<ID>" in reference:
565 reference = reference["<ID>"]
566 elif "*" in reference:
567 reference = reference["*"]
568 break
569 else:
570 raise NbiException("Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED)
571 if "TODO" in reference and method in reference["TODO"]:
572 raise NbiException("Method {} not supported yet for this URL".format(method), HTTPStatus.NOT_IMPLEMENTED)
tierno2236d202018-05-16 19:05:16 +0200573 elif "METHODS" in reference and method not in reference["METHODS"]:
tiernof27c79b2018-03-12 17:08:42 +0100574 raise NbiException("Method {} not supported for this URL".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
575 return
576
577 @staticmethod
tiernob24258a2018-10-04 18:39:49 +0200578 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +0100579 """
580 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +0200581 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +0100582 :param version:
tiernob24258a2018-10-04 18:39:49 +0200583 :param topic:
tiernof27c79b2018-03-12 17:08:42 +0100584 :param id:
585 :return: None
586 """
587 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
tiernob24258a2018-10-04 18:39:49 +0200588 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(main_topic, version, topic, id)
tiernof27c79b2018-03-12 17:08:42 +0100589 return
590
tiernoc94c3df2018-02-09 15:38:54 +0100591 @cherrypy.expose
tiernob24258a2018-10-04 18:39:49 +0200592 def default(self, main_topic=None, version=None, topic=None, _id=None, item=None, *args, **kwargs):
tiernoc94c3df2018-02-09 15:38:54 +0100593 session = None
tiernof27c79b2018-03-12 17:08:42 +0100594 outdata = None
595 _format = None
tierno0f98af52018-03-19 10:28:22 +0100596 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +0200597 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +0200598 rollback = []
599 session = None
tiernoc94c3df2018-02-09 15:38:54 +0100600 try:
tiernob24258a2018-10-04 18:39:49 +0200601 if not main_topic or not version or not topic:
602 raise NbiException("URL must contain at least 'main_topic/version/topic'",
603 HTTPStatus.METHOD_NOT_ALLOWED)
604 if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm"):
605 raise NbiException("URL main_topic '{}' not supported".format(main_topic),
606 HTTPStatus.METHOD_NOT_ALLOWED)
tiernoc94c3df2018-02-09 15:38:54 +0100607 if version != 'v1':
608 raise NbiException("URL version '{}' not supported".format(version), HTTPStatus.METHOD_NOT_ALLOWED)
609
tiernof27c79b2018-03-12 17:08:42 +0100610 if kwargs and "METHOD" in kwargs and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
611 method = kwargs.pop("METHOD")
612 else:
613 method = cherrypy.request.method
tiernob92094f2018-05-11 13:44:22 +0200614 if kwargs and "FORCE" in kwargs:
615 force = kwargs.pop("FORCE")
616 else:
617 force = False
tiernof27c79b2018-03-12 17:08:42 +0100618
tiernob24258a2018-10-04 18:39:49 +0200619 self._check_valid_url_method(method, main_topic, version, topic, _id, item, *args)
tiernof27c79b2018-03-12 17:08:42 +0100620
tiernob24258a2018-10-04 18:39:49 +0200621 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +0100622 return self.token(method, _id, kwargs)
623
tiernoc94c3df2018-02-09 15:38:54 +0100624 # self.engine.load_dbase(cherrypy.request.app.config)
Eduardo Sousa2f988212018-07-26 01:04:11 +0100625 session = self.authenticator.authorize()
tiernof27c79b2018-03-12 17:08:42 +0100626 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +0200627 engine_topic = topic
628 if topic == "subscriptions":
629 engine_topic = main_topic + "_" + topic
630 if item:
631 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +0100632
tiernob24258a2018-10-04 18:39:49 +0200633 if main_topic == "nsd":
634 engine_topic = "nsds"
635 elif main_topic == "vnfpkgm":
636 engine_topic = "vnfds"
637 elif main_topic == "nslcm":
638 engine_topic = "nsrs"
639 if topic == "ns_lcm_op_occs":
640 engine_topic = "nslcmops"
641 if topic == "vnfrs" or topic == "vnf_instances":
642 engine_topic = "vnfrs"
643 elif main_topic == "pdu":
644 engine_topic = "pdus"
645 if engine_topic == "vims": # TODO this is for backward compatibility, it will remove in the future
646 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +0100647
648 if method == "GET":
tiernob24258a2018-10-04 18:39:49 +0200649 if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd"):
650 if item in ("vnfd", "nsd"):
tiernof27c79b2018-03-12 17:08:42 +0100651 path = "$DESCRIPTOR"
652 elif args:
653 path = args
tiernob24258a2018-10-04 18:39:49 +0200654 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +0100655 path = ()
656 else:
657 path = None
tiernob24258a2018-10-04 18:39:49 +0200658 file, _format = self.engine.get_file(session, engine_topic, _id, path,
tierno2236d202018-05-16 19:05:16 +0200659 cherrypy.request.headers.get("Accept"))
tiernof27c79b2018-03-12 17:08:42 +0100660 outdata = file
661 elif not _id:
tiernob24258a2018-10-04 18:39:49 +0200662 outdata = self.engine.get_item_list(session, engine_topic, kwargs)
tiernoc94c3df2018-02-09 15:38:54 +0100663 else:
tiernob24258a2018-10-04 18:39:49 +0200664 outdata = self.engine.get_item(session, engine_topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100665 elif method == "POST":
tiernob24258a2018-10-04 18:39:49 +0200666 if topic in ("ns_descriptors_content", "vnf_packages_content"):
tiernof27c79b2018-03-12 17:08:42 +0100667 _id = cherrypy.request.headers.get("Transaction-Id")
668 if not _id:
tiernob24258a2018-10-04 18:39:49 +0200669 _id = self.engine.new_item(rollback, session, engine_topic, {}, None, cherrypy.request.headers,
tiernob92094f2018-05-11 13:44:22 +0200670 force=force)
tiernob24258a2018-10-04 18:39:49 +0200671 completed = self.engine.upload_content(session, engine_topic, _id, indata, kwargs,
672 cherrypy.request.headers, force=force)
tiernof27c79b2018-03-12 17:08:42 +0100673 if completed:
tiernob24258a2018-10-04 18:39:49 +0200674 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100675 else:
676 cherrypy.response.headers["Transaction-Id"] = _id
677 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +0200678 elif topic == "ns_instances_content":
679 # creates NSR
680 _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs, force=force)
681 # creates nslcmop
682 indata["lcmOperationType"] = "instantiate"
683 indata["nsInstanceId"] = _id
684 self.engine.new_item(rollback, session, "nslcmops", indata, None)
685 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100686 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +0200687 elif topic == "ns_instances" and item:
688 indata["lcmOperationType"] = item
689 indata["nsInstanceId"] = _id
690 _id = self.engine.new_item(rollback, session, "nslcmops", indata, kwargs)
691 self._set_location_header(main_topic, version, "ns_lcm_op_occs", _id)
tierno65acb4d2018-04-06 16:42:40 +0200692 outdata = {"id": _id}
693 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernof27c79b2018-03-12 17:08:42 +0100694 else:
tiernob24258a2018-10-04 18:39:49 +0200695 _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs,
696 cherrypy.request.headers, force=force)
697 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +0100698 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +0200699 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tiernof27c79b2018-03-12 17:08:42 +0100700 cherrypy.response.status = HTTPStatus.CREATED.value
tierno09c073e2018-04-26 13:36:48 +0200701
tiernoc94c3df2018-02-09 15:38:54 +0100702 elif method == "DELETE":
703 if not _id:
tiernob24258a2018-10-04 18:39:49 +0200704 outdata = self.engine.del_item_list(session, engine_topic, kwargs)
tierno09c073e2018-04-26 13:36:48 +0200705 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +0100706 else: # len(args) > 1
tiernob24258a2018-10-04 18:39:49 +0200707 if topic == "ns_instances_content" and not force:
708 nslcmop_desc = {
709 "lcmOperationType": "terminate",
710 "nsInstanceId": _id,
711 "autoremove": True
712 }
713 opp_id = self.engine.new_item(rollback, session, "nslcmops", nslcmop_desc, None)
tierno3ace63c2018-05-03 17:51:43 +0200714 outdata = {"_id": opp_id}
tierno09c073e2018-04-26 13:36:48 +0200715 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tierno65acb4d2018-04-06 16:42:40 +0200716 else:
tiernob24258a2018-10-04 18:39:49 +0200717 self.engine.del_item(session, engine_topic, _id, force)
tierno09c073e2018-04-26 13:36:48 +0200718 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernob24258a2018-10-04 18:39:49 +0200719 if engine_topic in ("vim_accounts", "sdns"):
tierno09c073e2018-04-26 13:36:48 +0200720 cherrypy.response.status = HTTPStatus.ACCEPTED.value
721
tierno7ae10112018-05-18 14:36:02 +0200722 elif method in ("PUT", "PATCH"):
tiernocd54a4a2018-09-12 16:40:35 +0200723 outdata = None
tiernof27c79b2018-03-12 17:08:42 +0100724 if not indata and not kwargs:
tiernoc94c3df2018-02-09 15:38:54 +0100725 raise NbiException("Nothing to update. Provide payload and/or query string",
726 HTTPStatus.BAD_REQUEST)
tiernob24258a2018-10-04 18:39:49 +0200727 if item in ("nsd_content", "package_content") and method == "PUT":
728 completed = self.engine.upload_content(session, engine_topic, _id, indata, kwargs,
729 cherrypy.request.headers, force=force)
tiernof27c79b2018-03-12 17:08:42 +0100730 if not completed:
731 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +0100732 else:
tiernob24258a2018-10-04 18:39:49 +0200733 self.engine.edit_item(session, engine_topic, _id, indata, kwargs, force=force)
tiernocd54a4a2018-09-12 16:40:35 +0200734 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernoc94c3df2018-02-09 15:38:54 +0100735 else:
736 raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
tiernof27c79b2018-03-12 17:08:42 +0100737 return self._format_out(outdata, session, _format)
tiernob24258a2018-10-04 18:39:49 +0200738 except Exception as e:
739 if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException)):
740 http_code_value = cherrypy.response.status = e.http_code.value
741 http_code_name = e.http_code.name
742 cherrypy.log("Exception {}".format(e))
743 else:
744 http_code_value = cherrypy.response.status = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
745 cherrypy.log("CRITICAL: Exception {}".format(e))
746 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +0200747 if hasattr(outdata, "close"): # is an open file
748 outdata.close()
tiernob24258a2018-10-04 18:39:49 +0200749 error_text = str(e)
750 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +0200751 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +0200752 try:
tiernocc103432018-10-19 14:10:35 +0200753 if rollback_item.get("operation") == "set":
754 self.engine.db.set_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
755 rollback_item["content"], fail_on_empty=False)
756 else:
757 self.engine.del_item(**rollback_item, session=session, force=True)
tierno3ace63c2018-05-03 17:51:43 +0200758 except Exception as e2:
tiernob24258a2018-10-04 18:39:49 +0200759 rollback_error_text = "Rollback Exception {}: {}".format(rollback_item, e2)
760 cherrypy.log(rollback_error_text)
761 error_text += ". " + rollback_error_text
762 # if isinstance(e, MsgException):
763 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
764 # engine_topic[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +0100765 problem_details = {
tiernob24258a2018-10-04 18:39:49 +0200766 "code": http_code_name,
767 "status": http_code_value,
768 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +0100769 }
770 return self._format_out(problem_details, session)
771 # raise cherrypy.HTTPError(e.http_code.value, str(e))
772
773
774# def validate_password(realm, username, password):
775# cherrypy.log("realm "+ str(realm))
776# if username == "admin" and password == "admin":
777# return True
778# return False
779
780
781def _start_service():
782 """
783 Callback function called when cherrypy.engine starts
784 Override configuration with env variables
785 Set database, storage, message configuration
786 Init database with admin/admin user password
787 """
788 cherrypy.log.error("Starting osm_nbi")
789 # update general cherrypy configuration
790 update_dict = {}
791
792 engine_config = cherrypy.tree.apps['/osm'].config
793 for k, v in environ.items():
794 if not k.startswith("OSMNBI_"):
795 continue
tiernoe1281182018-05-22 12:24:36 +0200796 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +0100797 if not k2:
798 continue
799 try:
800 # update static configuration
801 if k == 'OSMNBI_STATIC_DIR':
802 engine_config["/static"]['tools.staticdir.dir'] = v
803 engine_config["/static"]['tools.staticdir.on'] = True
804 elif k == 'OSMNBI_SOCKET_PORT' or k == 'OSMNBI_SERVER_PORT':
805 update_dict['server.socket_port'] = int(v)
806 elif k == 'OSMNBI_SOCKET_HOST' or k == 'OSMNBI_SERVER_HOST':
807 update_dict['server.socket_host'] = v
tiernof5298be2018-05-16 14:43:57 +0200808 elif k1 in ("server", "test", "auth", "log"):
809 update_dict[k1 + '.' + k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100810 elif k1 in ("message", "database", "storage", "authentication"):
tiernof5298be2018-05-16 14:43:57 +0200811 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100812 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +0100813 engine_config[k1][k2] = int(v)
814 else:
815 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100816
tiernoc94c3df2018-02-09 15:38:54 +0100817 except ValueError as e:
818 cherrypy.log.error("Ignoring environ '{}': " + str(e))
819 except Exception as e:
820 cherrypy.log.warn("skipping environ '{}' on exception '{}'".format(k, e))
821
822 if update_dict:
823 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +0200824 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +0100825
826 # logging cherrypy
827 log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
828 log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
829 logger_server = logging.getLogger("cherrypy.error")
830 logger_access = logging.getLogger("cherrypy.access")
831 logger_cherry = logging.getLogger("cherrypy")
832 logger_nbi = logging.getLogger("nbi")
833
tiernof5298be2018-05-16 14:43:57 +0200834 if "log.file" in engine_config["global"]:
835 file_handler = logging.handlers.RotatingFileHandler(engine_config["global"]["log.file"],
tiernoc94c3df2018-02-09 15:38:54 +0100836 maxBytes=100e6, backupCount=9, delay=0)
837 file_handler.setFormatter(log_formatter_simple)
838 logger_cherry.addHandler(file_handler)
839 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +0200840 # log always to standard output
841 for format_, logger in {"nbi.server %(filename)s:%(lineno)s": logger_server,
842 "nbi.access %(filename)s:%(lineno)s": logger_access,
843 "%(name)s %(filename)s:%(lineno)s": logger_nbi
844 }.items():
845 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
846 log_formatter_cherry = logging.Formatter(log_format_cherry, datefmt='%Y-%m-%dT%H:%M:%S')
847 str_handler = logging.StreamHandler()
848 str_handler.setFormatter(log_formatter_cherry)
849 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +0100850
tiernof5298be2018-05-16 14:43:57 +0200851 if engine_config["global"].get("log.level"):
852 logger_cherry.setLevel(engine_config["global"]["log.level"])
853 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +0100854
855 # logging other modules
856 for k1, logname in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
857 engine_config[k1]["logger_name"] = logname
858 logger_module = logging.getLogger(logname)
859 if "logfile" in engine_config[k1]:
860 file_handler = logging.handlers.RotatingFileHandler(engine_config[k1]["logfile"],
tierno2236d202018-05-16 19:05:16 +0200861 maxBytes=100e6, backupCount=9, delay=0)
tiernoc94c3df2018-02-09 15:38:54 +0100862 file_handler.setFormatter(log_formatter_simple)
863 logger_module.addHandler(file_handler)
864 if "loglevel" in engine_config[k1]:
865 logger_module.setLevel(engine_config[k1]["loglevel"])
866 # TODO add more entries, e.g.: storage
867 cherrypy.tree.apps['/osm'].root.engine.start(engine_config)
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100868 cherrypy.tree.apps['/osm'].root.authenticator.start(engine_config)
tiernod985a8d2018-10-19 14:12:28 +0200869 cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version)
870 cherrypy.tree.apps['/osm'].root.authenticator.init_db(target_version=auth_database_version)
tiernoc94c3df2018-02-09 15:38:54 +0100871 # getenv('OSMOPENMANO_TENANT', None)
872
873
874def _stop_service():
875 """
876 Callback function called when cherrypy.engine stops
877 TODO: Ending database connections.
878 """
879 cherrypy.tree.apps['/osm'].root.engine.stop()
880 cherrypy.log.error("Stopping osm_nbi")
881
tierno2236d202018-05-16 19:05:16 +0200882
tiernof5298be2018-05-16 14:43:57 +0200883def nbi(config_file):
tiernoc94c3df2018-02-09 15:38:54 +0100884 # conf = {
885 # '/': {
886 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
887 # 'tools.sessions.on': True,
888 # 'tools.response_headers.on': True,
889 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
890 # }
891 # }
892 # cherrypy.Server.ssl_module = 'builtin'
893 # cherrypy.Server.ssl_certificate = "http/cert.pem"
894 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
895 # cherrypy.Server.thread_pool = 10
896 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
897
898 # cherrypy.config.update({'tools.auth_basic.on': True,
899 # 'tools.auth_basic.realm': 'localhost',
900 # 'tools.auth_basic.checkpassword': validate_password})
901 cherrypy.engine.subscribe('start', _start_service)
902 cherrypy.engine.subscribe('stop', _stop_service)
tiernof5298be2018-05-16 14:43:57 +0200903 cherrypy.quickstart(Server(), '/osm', config_file)
904
905
906def usage():
907 print("""Usage: {} [options]
908 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
909 -h|--help: shows this help
910 """.format(sys.argv[0]))
tierno2236d202018-05-16 19:05:16 +0200911 # --log-socket-host HOST: send logs to this host")
912 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +0100913
914
915if __name__ == '__main__':
tiernof5298be2018-05-16 14:43:57 +0200916 try:
917 # load parameters and configuration
918 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
919 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
920 config_file = None
921 for o, a in opts:
922 if o in ("-h", "--help"):
923 usage()
924 sys.exit()
925 elif o in ("-c", "--config"):
926 config_file = a
927 # elif o == "--log-socket-port":
928 # log_socket_port = a
929 # elif o == "--log-socket-host":
930 # log_socket_host = a
931 # elif o == "--log-file":
932 # log_file = a
933 else:
934 assert False, "Unhandled option"
935 if config_file:
936 if not path.isfile(config_file):
937 print("configuration file '{}' that not exist".format(config_file), file=sys.stderr)
938 exit(1)
939 else:
940 for config_file in (__file__[:__file__.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
941 if path.isfile(config_file):
942 break
943 else:
944 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys.stderr)
945 exit(1)
946 nbi(config_file)
947 except getopt.GetoptError as e:
948 print(str(e), file=sys.stderr)
949 # usage()
950 exit(1)