2 # -*- coding: utf-8 -*-
8 import html_out
as html
10 from engine
import Engine
, EngineException
11 from dbbase
import DbException
12 from fsbase
import FsException
13 from base64
import standard_b64decode
14 #from os import getenv
15 from http
import HTTPStatus
16 #from http.client import responses as http_responses
17 from codecs
import getreader
18 from os
import environ
20 __author__
= "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
22 version_date
= "Mar 2018"
25 North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
26 URL: /osm GET POST PUT DELETE PATCH
28 /ns_descriptors_content O O
34 /artifacts[/<artifactPath>] O
44 /package_content O5 O5
47 /artifacts[/<artifactPath>] O5
52 /ns_instances_content O O
71 <attrName>[.<attrName>...]*[.<op>]=<value>[,<value>...]&...
72 op: "eq"(or empty to one or the values) | "neq" (to any of the values) | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
73 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
74 (none) … same as “exclude_default”
75 all_fields … all attributes.
76 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not conditionally mandatory, and that are not provided in <list>.
77 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that are not conditionally mandatory, and that are provided in <list>.
78 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for the particular resource
79 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the present specification for the particular resource, but that are not part of <list>
80 Header field name Reference Example Descriptions
81 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
82 This header field shall be present if the response is expected to have a non-empty message body.
83 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
84 This header field shall be present if the request has a non-empty message body.
85 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request. Details are specified in clause 4.5.3.
86 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
87 Header field name Reference Example Descriptions
88 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
89 This header field shall be present if the response has a non-empty message body.
90 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a new resource has been created.
91 This header field shall be present if the response status code is 201 or 3xx.
92 In the present document this header field is also used if the response status code is 202 and a new resource was created.
93 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization token.
94 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for certain resources.
95 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the response, and the total length of the file.
96 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
100 120 Used to indicate how long the user agent ought to wait before making a follow-up request.
101 It can be used with 503 responses.
102 The value of this field can be an HTTP-date or a number of seconds to delay after the response is received.
104 #TODO http header for partial uploads: Content-Range: "bytes 0-1199/15000". Id is returned first time and send in following chunks
108 class NbiException(Exception):
110 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
111 Exception.__init
__(self
, message
)
112 self
.http_code
= http_code
115 class Server(object):
117 # to decode bytes to str
118 reader
= getreader("utf-8")
122 self
.engine
= Engine()
123 self
.valid_methods
= { # contains allowed URL and methods
126 "tokens": { "METHODS": ("GET", "POST", "DELETE"),
127 "<ID>": { "METHODS": ("GET", "DELETE")}
129 "users": { "METHODS": ("GET", "POST"),
130 "<ID>": {"METHODS": ("GET", "POST", "DELETE")}
132 "projects": { "METHODS": ("GET", "POST"),
133 "<ID>": {"METHODS": ("GET", "POST", "DELETE")}
139 "ns_descriptors_content": { "METHODS": ("GET", "POST"),
140 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
142 "ns_descriptors": { "METHODS": ("GET", "POST"),
143 "<ID>": { "METHODS": ("GET", "DELETE"), "TODO": "PATCH",
144 "nsd_content": { "METHODS": ("GET", "PUT")},
145 "nsd": {"METHODS": "GET"}, # descriptor inside package
146 "artifacts": {"*": {"METHODS": "GET"}}
150 "pnf_descriptors": {"TODO": ("GET", "POST"),
151 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
152 "pnfd_content": {"TODO": ("GET", "PUT")}
155 "subscriptions": {"TODO": ("GET", "POST"),
156 "<ID>": {"TODO": ("GET", "DELETE"),}
162 "vnf_packages_content": { "METHODS": ("GET", "POST"),
163 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
165 "vnf_packages": { "METHODS": ("GET", "POST"),
166 "<ID>": { "METHODS": ("GET", "DELETE"), "TODO": "PATCH", # GET: vnfPkgInfo
167 "package_content": { "METHODS": ("GET", "PUT"), # package
168 "upload_from_uri": {"TODO": "POST"}
170 "vnfd": {"METHODS": "GET"}, # descriptor inside package
171 "artifacts": {"*": {"METHODS": "GET"}}
175 "subscriptions": {"TODO": ("GET", "POST"),
176 "<ID>": {"TODO": ("GET", "DELETE"),}
182 "ns_instances_content": {"METHODS": ("GET", "POST"),
183 "<ID>": {"METHODS": ("GET", "DELETE")}
185 "ns_instances": {"TODO": ("GET", "POST"),
186 "<ID>": {"TODO": ("GET", "DELETE")}
192 def _authorization(self
):
196 # 1. Get token Authorization bearer
197 auth
= cherrypy
.request
.headers
.get("Authorization")
199 auth_list
= auth
.split(" ")
200 if auth_list
[0].lower() == "bearer":
201 token
= auth_list
[-1]
202 elif auth_list
[0].lower() == "basic":
203 user_passwd64
= auth_list
[-1]
205 if cherrypy
.session
.get("Authorization"):
206 # 2. Try using session before request a new token. If not, basic authentication will generate
207 token
= cherrypy
.session
.get("Authorization")
208 if token
== "logout":
209 token
= None # force Unauthorized response to insert user pasword again
210 elif user_passwd64
and cherrypy
.request
.config
.get("auth.allow_basic_authentication"):
211 # 3. Get new token from user password
215 user_passwd
= standard_b64decode(user_passwd64
).decode()
216 user
, _
, passwd
= user_passwd
.partition(":")
219 outdata
= self
.engine
.new_token(None, {"username": user
, "password": passwd
})
220 token
= outdata
["id"]
221 cherrypy
.session
['Authorization'] = token
222 # 4. Get token from cookie
224 # auth_cookie = cherrypy.request.cookie.get("Authorization")
226 # token = auth_cookie.value
227 return self
.engine
.authorize(token
)
228 except EngineException
as e
:
229 if cherrypy
.session
.get('Authorization'):
230 del cherrypy
.session
['Authorization']
231 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Bearer realm="{}"'.format(e
)
234 def _format_in(self
, kwargs
):
237 if cherrypy
.request
.body
.length
:
238 error_text
= "Invalid input format "
240 if "Content-Type" in cherrypy
.request
.headers
:
241 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
242 error_text
= "Invalid json format "
243 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
244 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
245 error_text
= "Invalid yaml format "
246 indata
= yaml
.load(cherrypy
.request
.body
)
247 elif "application/binary" in cherrypy
.request
.headers
["Content-Type"] or \
248 "application/gzip" in cherrypy
.request
.headers
["Content-Type"] or \
249 "application/zip" in cherrypy
.request
.headers
["Content-Type"] or \
250 "text/plain" in cherrypy
.request
.headers
["Content-Type"]:
251 indata
= cherrypy
.request
.body
# .read()
252 elif "multipart/form-data" in cherrypy
.request
.headers
["Content-Type"]:
253 if "descriptor_file" in kwargs
:
254 filecontent
= kwargs
.pop("descriptor_file")
255 if not filecontent
.file:
256 raise NbiException("empty file or content", HTTPStatus
.BAD_REQUEST
)
257 indata
= filecontent
.file # .read()
258 if filecontent
.content_type
.value
:
259 cherrypy
.request
.headers
["Content-Type"] = filecontent
.content_type
.value
261 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
262 # "Only 'Content-Type' of type 'application/json' or
263 # 'application/yaml' for input format are available")
264 error_text
= "Invalid yaml format "
265 indata
= yaml
.load(cherrypy
.request
.body
)
267 error_text
= "Invalid yaml format "
268 indata
= yaml
.load(cherrypy
.request
.body
)
273 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
276 for k
, v
in kwargs
.items():
277 if isinstance(v
, str):
282 kwargs
[k
] = yaml
.load(v
)
285 elif k
.endswith(".gt") or k
.endswith(".lt") or k
.endswith(".gte") or k
.endswith(".lte"):
293 elif v
.find(",") > 0:
294 kwargs
[k
] = v
.split(",")
295 elif isinstance(v
, (list, tuple)):
296 for index
in range(0, len(v
)):
301 v
[index
] = yaml
.load(v
[index
])
306 except (ValueError, yaml
.YAMLError
) as exc
:
307 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
308 except KeyError as exc
:
309 raise NbiException("Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
)
312 def _format_out(data
, session
=None, _format
=None):
314 return string of dictionary data according to requested json, yaml, xml. By default json
315 :param data: response to be sent. Can be a dict, text or file
317 :param _format: The format to be set as Content-Type ir data is a file
321 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
323 elif hasattr(data
, "read"): # file object
325 cherrypy
.response
.headers
["Content-Type"] = _format
326 elif "b" in data
.mode
: # binariy asssumig zip
327 cherrypy
.response
.headers
["Content-Type"] = 'application/zip'
329 cherrypy
.response
.headers
["Content-Type"] = 'text/plain'
330 # TODO check that cherrypy close file. If not implement pending things to close per thread next
332 if "Accept" in cherrypy
.request
.headers
:
333 accept
= cherrypy
.request
.headers
["Accept"]
334 if "application/json" in accept
:
335 cherrypy
.response
.headers
["Content-Type"] = 'application/json; charset=utf-8'
336 a
= json
.dumps(data
, indent
=4) + "\n"
337 return a
.encode("utf8")
338 elif "text/html" in accept
:
339 return html
.format(data
, cherrypy
.request
, cherrypy
.response
, session
)
341 elif "application/yaml" in accept
or "*/*" in accept
or "text/plain" in accept
:
344 raise cherrypy
.HTTPError(HTTPStatus
.NOT_ACCEPTABLE
.value
,
345 "Only 'Accept' of type 'application/json' or 'application/yaml' "
346 "for output format are available")
347 cherrypy
.response
.headers
["Content-Type"] = 'application/yaml'
348 return yaml
.safe_dump(data
, explicit_start
=True, indent
=4, default_flow_style
=False, tags
=False,
349 encoding
='utf-8', allow_unicode
=True) # , canonical=True, default_style='"'
352 def index(self
, *args
, **kwargs
):
355 if cherrypy
.request
.method
== "GET":
356 session
= self
._authorization
()
357 outdata
= "Index page"
359 raise cherrypy
.HTTPError(HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
360 "Method {} not allowed for tokens".format(cherrypy
.request
.method
))
362 return self
._format
_out
(outdata
, session
)
364 except EngineException
as e
:
365 cherrypy
.log("index Exception {}".format(e
))
366 cherrypy
.response
.status
= e
.http_code
.value
367 return self
._format
_out
("Welcome to OSM!", session
)
370 def token(self
, method
, token_id
=None, kwargs
=None):
372 # self.engine.load_dbase(cherrypy.request.app.config)
373 indata
= self
._format
_in
(kwargs
)
374 if not isinstance(indata
, dict):
375 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus
.BAD_REQUEST
)
378 session
= self
._authorization
()
380 outdata
= self
.engine
.get_token(session
, token_id
)
382 outdata
= self
.engine
.get_token_list(session
)
383 elif method
== "POST":
385 session
= self
._authorization
()
389 indata
.update(kwargs
)
390 outdata
= self
.engine
.new_token(session
, indata
, cherrypy
.request
.remote
)
392 cherrypy
.session
['Authorization'] = outdata
["_id"]
393 # cherrypy.response.cookie["Authorization"] = outdata["id"]
394 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
395 elif method
== "DELETE":
396 if not token_id
and "id" in kwargs
:
397 token_id
= kwargs
["id"]
399 session
= self
._authorization
()
400 token_id
= session
["_id"]
401 outdata
= self
.engine
.del_token(token_id
)
403 cherrypy
.session
['Authorization'] = "logout"
404 # cherrypy.response.cookie["Authorization"] = token_id
405 # cherrypy.response.cookie["Authorization"]['expires'] = 0
407 raise NbiException("Method {} not allowed for token".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
408 return self
._format
_out
(outdata
, session
)
409 except (NbiException
, EngineException
, DbException
) as e
:
410 cherrypy
.log("tokens Exception {}".format(e
))
411 cherrypy
.response
.status
= e
.http_code
.value
413 "code": e
.http_code
.name
,
414 "status": e
.http_code
.value
,
417 return self
._format
_out
(problem_details
, session
)
420 def test2(self
, args0
=None, args1
=None, args2
=None, args3
=None, *args
, **kwargs
):
422 "<html><pre>\n{} {} {} {} {} {} \n".format(args0
, args1
, args2
, args3
, args
, kwargs
))
423 return_text
+= "</pre></html>"
427 def test(self
, *args
, **kwargs
):
429 if args
and args
[0] == "help":
430 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nprune\nlogin\nlogin2\n"\
431 "sleep/<time>\n</pre></html>"
433 elif args
and args
[0] == "init":
435 # self.engine.load_dbase(cherrypy.request.app.config)
436 self
.engine
.create_admin()
437 return "Done. User 'admin', password 'admin' created"
439 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
440 return self
._format
_out
("Database already initialized")
441 elif args
and args
[0] == "file":
442 return cherrypy
.lib
.static
.serve_file(cherrypy
.tree
.apps
['/osm'].config
["storage"]["path"] + "/" + args
[1],
443 "text/plain", "attachment")
444 elif args
and args
[0] == "file2":
445 f_path
= cherrypy
.tree
.apps
['/osm'].config
["storage"]["path"] + "/" + args
[1]
446 f
= open(f_path
, "r")
447 cherrypy
.response
.headers
["Content-type"] = "text/plain"
450 elif len(args
) == 2 and args
[0] == "db-clear":
451 return self
.engine
.del_item_list({"project_id": "admin"}, args
[1], {})
452 elif args
and args
[0] == "prune":
453 return self
.engine
.prune()
454 elif args
and args
[0] == "login":
455 if not cherrypy
.request
.headers
.get("Authorization"):
456 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
457 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
458 elif args
and args
[0] == "login2":
459 if not cherrypy
.request
.headers
.get("Authorization"):
460 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
461 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
462 elif args
and args
[0] == "sleep":
465 sleep_time
= int(args
[1])
467 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
468 return self
._format
_out
("Database already initialized")
469 thread_info
= cherrypy
.thread_data
471 time
.sleep(sleep_time
)
473 elif len(args
) >= 2 and args
[0] == "message":
476 for k
, v
in kwargs
.items():
477 self
.engine
.msg
.write(topic
, k
, yaml
.load(v
))
479 except Exception as e
:
480 return "Error: " + format(e
)
483 "<html><pre>\nheaders:\n args: {}\n".format(args
) +
484 " kwargs: {}\n".format(kwargs
) +
485 " headers: {}\n".format(cherrypy
.request
.headers
) +
486 " path_info: {}\n".format(cherrypy
.request
.path_info
) +
487 " query_string: {}\n".format(cherrypy
.request
.query_string
) +
488 " session: {}\n".format(cherrypy
.session
) +
489 " cookie: {}\n".format(cherrypy
.request
.cookie
) +
490 " method: {}\n".format(cherrypy
.request
.method
) +
491 " session: {}\n".format(cherrypy
.session
.get('fieldname')) +
493 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
494 if cherrypy
.request
.body
.length
:
495 return_text
+= " content: {}\n".format(
496 str(cherrypy
.request
.body
.read(int(cherrypy
.request
.headers
.get('Content-Length', 0)))))
498 return_text
+= "thread: {}\n".format(thread_info
)
499 return_text
+= "</pre></html>"
502 def _check_valid_url_method(self
, method
, *args
):
504 raise NbiException("URL must contain at least 'topic/version/item'", HTTPStatus
.METHOD_NOT_ALLOWED
)
506 reference
= self
.valid_methods
510 if not isinstance(reference
, dict):
511 raise NbiException("URL contains unexpected extra items '{}'".format(arg
),
512 HTTPStatus
.METHOD_NOT_ALLOWED
)
515 reference
= reference
[arg
]
516 elif "<ID>" in reference
:
517 reference
= reference
["<ID>"]
518 elif "*" in reference
:
519 reference
= reference
["*"]
522 raise NbiException("Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
)
523 if "TODO" in reference
and method
in reference
["TODO"]:
524 raise NbiException("Method {} not supported yet for this URL".format(method
), HTTPStatus
.NOT_IMPLEMENTED
)
525 elif "METHODS" in reference
and not method
in reference
["METHODS"]:
526 raise NbiException("Method {} not supported for this URL".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
530 def _set_location_header(topic
, version
, item
, id):
532 Insert response header Location with the URL of created item base on URL params
539 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
540 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(topic
, version
, item
, id)
544 def default(self
, topic
=None, version
=None, item
=None, _id
=None, item2
=None, *args
, **kwargs
):
549 if not topic
or not version
or not item
:
550 raise NbiException("URL must contain at least 'topic/version/item'", HTTPStatus
.METHOD_NOT_ALLOWED
)
551 if topic
not in ("admin", "vnfpkgm", "nsd", "nslcm"):
552 raise NbiException("URL topic '{}' not supported".format(topic
), HTTPStatus
.METHOD_NOT_ALLOWED
)
554 raise NbiException("URL version '{}' not supported".format(version
), HTTPStatus
.METHOD_NOT_ALLOWED
)
556 if kwargs
and "METHOD" in kwargs
and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
557 method
= kwargs
.pop("METHOD")
559 method
= cherrypy
.request
.method
561 self
._check
_valid
_url
_method
(method
, topic
, version
, item
, _id
, item2
, *args
)
563 if topic
== "admin" and item
== "tokens":
564 return self
.token(method
, _id
, kwargs
)
566 # self.engine.load_dbase(cherrypy.request.app.config)
567 session
= self
._authorization
()
568 indata
= self
._format
_in
(kwargs
)
570 if item
== "subscriptions":
571 engine_item
= topic
+ "_" + item
577 elif topic
== "vnfpkgm":
578 engine_item
= "vnfds"
579 elif topic
== "nslcm":
583 if item2
in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd"):
584 if item2
in ("vnfd", "nsd"):
588 elif item2
== "artifacts":
592 file, _format
= self
.engine
.get_file(session
, engine_item
, _id
, path
,
593 cherrypy
.request
.headers
.get("Accept"))
596 outdata
= self
.engine
.get_item_list(session
, engine_item
, kwargs
)
598 outdata
= self
.engine
.get_item(session
, engine_item
, _id
)
599 elif method
== "POST":
600 if item
in ("ns_descriptors_content", "vnf_packages_content"):
601 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
603 _id
= self
.engine
.new_item(session
, engine_item
, {}, None, cherrypy
.request
.headers
)
604 completed
= self
.engine
.upload_content(session
, engine_item
, _id
, indata
, kwargs
, cherrypy
.request
.headers
)
606 self
._set
_location
_header
(topic
, version
, item
, _id
)
608 cherrypy
.response
.headers
["Transaction-Id"] = _id
609 outdata
= {"id": _id
}
610 elif item
in ("ns_descriptors", "vnf_packages"):
611 _id
= self
.engine
.new_item(session
, engine_item
, indata
, kwargs
, cherrypy
.request
.headers
)
612 self
._set
_location
_header
(topic
, version
, item
, _id
)
614 outdata
= {"id": _id
}
616 _id
= self
.engine
.new_item(session
, engine_item
, indata
, kwargs
, cherrypy
.request
.headers
)
617 self
._set
_location
_header
(topic
, version
, item
, _id
)
618 outdata
= {"id": _id
}
619 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
620 elif method
== "DELETE":
622 outdata
= self
.engine
.del_item_list(session
, engine_item
, kwargs
)
623 else: # len(args) > 1
624 outdata
= self
.engine
.del_item(session
, engine_item
, _id
)
625 if item
in ("ns_descriptors", "vnf_packages"): # SOL005
627 elif method
== "PUT":
628 if not indata
and not kwargs
:
629 raise NbiException("Nothing to update. Provide payload and/or query string",
630 HTTPStatus
.BAD_REQUEST
)
631 if item2
in ("nsd_content", "package_content"):
632 completed
= self
.engine
.upload_content(session
, engine_item
, _id
, indata
, kwargs
, cherrypy
.request
.headers
)
634 cherrypy
.response
.headers
["Transaction-Id"] = id
637 outdata
= {"id": self
.engine
.edit_item(session
, engine_item
, args
[1], indata
, kwargs
)}
639 raise NbiException("Method {} not allowed".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
640 return self
._format
_out
(outdata
, session
, _format
)
641 except (NbiException
, EngineException
, DbException
, FsException
) as e
:
642 if hasattr(outdata
, "close"): # is an open file
644 cherrypy
.log("Exception {}".format(e
))
645 cherrypy
.response
.status
= e
.http_code
.value
647 "code": e
.http_code
.name
,
648 "status": e
.http_code
.value
,
651 return self
._format
_out
(problem_details
, session
)
652 # raise cherrypy.HTTPError(e.http_code.value, str(e))
655 # def validate_password(realm, username, password):
656 # cherrypy.log("realm "+ str(realm))
657 # if username == "admin" and password == "admin":
662 def _start_service():
664 Callback function called when cherrypy.engine starts
665 Override configuration with env variables
666 Set database, storage, message configuration
667 Init database with admin/admin user password
669 cherrypy
.log
.error("Starting osm_nbi")
670 # update general cherrypy configuration
673 engine_config
= cherrypy
.tree
.apps
['/osm'].config
674 for k
, v
in environ
.items():
675 if not k
.startswith("OSMNBI_"):
677 k1
, _
, k2
= k
[7:].lower().partition("_")
681 # update static configuration
682 if k
== 'OSMNBI_STATIC_DIR':
683 engine_config
["/static"]['tools.staticdir.dir'] = v
684 engine_config
["/static"]['tools.staticdir.on'] = True
685 elif k
== 'OSMNBI_SOCKET_PORT' or k
== 'OSMNBI_SERVER_PORT':
686 update_dict
['server.socket_port'] = int(v
)
687 elif k
== 'OSMNBI_SOCKET_HOST' or k
== 'OSMNBI_SERVER_HOST':
688 update_dict
['server.socket_host'] = v
690 update_dict
['server' + k2
] = v
691 # TODO add more entries
692 elif k1
in ("message", "database", "storage"):
694 engine_config
[k1
][k2
] = int(v
)
696 engine_config
[k1
][k2
] = v
697 except ValueError as e
:
698 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
699 except Exception as e
:
700 cherrypy
.log
.warn("skipping environ '{}' on exception '{}'".format(k
, e
))
703 cherrypy
.config
.update(update_dict
)
706 log_format_simple
= "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
707 log_formatter_simple
= logging
.Formatter(log_format_simple
, datefmt
='%Y-%m-%dT%H:%M:%S')
708 logger_server
= logging
.getLogger("cherrypy.error")
709 logger_access
= logging
.getLogger("cherrypy.access")
710 logger_cherry
= logging
.getLogger("cherrypy")
711 logger_nbi
= logging
.getLogger("nbi")
713 if "logfile" in engine_config
["global"]:
714 file_handler
= logging
.handlers
.RotatingFileHandler(engine_config
["global"]["logfile"],
715 maxBytes
=100e6
, backupCount
=9, delay
=0)
716 file_handler
.setFormatter(log_formatter_simple
)
717 logger_cherry
.addHandler(file_handler
)
718 logger_nbi
.addHandler(file_handler
)
720 for format_
, logger
in {"nbi.server": logger_server
,
721 "nbi.access": logger_access
,
722 "%(name)s %(filename)s:%(lineno)s": logger_nbi
724 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
725 log_formatter_cherry
= logging
.Formatter(log_format_cherry
, datefmt
='%Y-%m-%dT%H:%M:%S')
726 str_handler
= logging
.StreamHandler()
727 str_handler
.setFormatter(log_formatter_cherry
)
728 logger
.addHandler(str_handler
)
730 if engine_config
["global"].get("loglevel"):
731 logger_cherry
.setLevel(engine_config
["global"]["loglevel"])
732 logger_nbi
.setLevel(engine_config
["global"]["loglevel"])
734 # logging other modules
735 for k1
, logname
in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
736 engine_config
[k1
]["logger_name"] = logname
737 logger_module
= logging
.getLogger(logname
)
738 if "logfile" in engine_config
[k1
]:
739 file_handler
= logging
.handlers
.RotatingFileHandler(engine_config
[k1
]["logfile"],
740 maxBytes
=100e6
, backupCount
=9, delay
=0)
741 file_handler
.setFormatter(log_formatter_simple
)
742 logger_module
.addHandler(file_handler
)
743 if "loglevel" in engine_config
[k1
]:
744 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
745 # TODO add more entries, e.g.: storage
746 cherrypy
.tree
.apps
['/osm'].root
.engine
.start(engine_config
)
748 cherrypy
.tree
.apps
['/osm'].root
.engine
.create_admin()
749 except EngineException
:
751 # getenv('OSMOPENMANO_TENANT', None)
756 Callback function called when cherrypy.engine stops
757 TODO: Ending database connections.
759 cherrypy
.tree
.apps
['/osm'].root
.engine
.stop()
760 cherrypy
.log
.error("Stopping osm_nbi")
765 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
766 # 'tools.sessions.on': True,
767 # 'tools.response_headers.on': True,
768 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
771 # cherrypy.Server.ssl_module = 'builtin'
772 # cherrypy.Server.ssl_certificate = "http/cert.pem"
773 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
774 # cherrypy.Server.thread_pool = 10
775 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
777 # cherrypy.config.update({'tools.auth_basic.on': True,
778 # 'tools.auth_basic.realm': 'localhost',
779 # 'tools.auth_basic.checkpassword': validate_password})
780 cherrypy
.engine
.subscribe('start', _start_service
)
781 cherrypy
.engine
.subscribe('stop', _stop_service
)
782 cherrypy
.quickstart(Server(), '/osm', "nbi.cfg")
785 if __name__
== '__main__':