06b39f3049a676301ab07d4b0ba5f2fd63b2dde2
2 # -*- coding: utf-8 -*-
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
21 import osm_nbi
.html_out
as html
23 import logging
.handlers
27 from osm_nbi
.authconn
import AuthException
, AuthconnException
28 from osm_nbi
.auth
import Authenticator
29 from osm_nbi
.engine
import Engine
, EngineException
30 from osm_nbi
.subscriptions
import SubscriptionThread
31 from osm_nbi
.validation
import ValidationError
32 from osm_common
.dbbase
import DbException
33 from osm_common
.fsbase
import FsException
34 from osm_common
.msgbase
import MsgException
35 from http
import HTTPStatus
36 from codecs
import getreader
37 from os
import environ
, path
38 from osm_nbi
import version
as nbi_version
, version_date
as nbi_version_date
40 __author__
= "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
42 __version__
= "0.1.3" # file version, not NBI version
43 version_date
= "Aug 2019"
45 database_version
= '1.2'
46 auth_database_version
= '1.0'
47 nbi_server
= None # instance of Server class
48 subscription_thread
= None # instance of SubscriptionThread class
51 North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
52 URL: /osm GET POST PUT DELETE PATCH
54 /ns_descriptors_content O O
60 /artifacts[/<artifactPath>] O
68 /vnf_packages_content O O
72 /package_content O5 O5
75 /artifacts[/<artifactPath>] O5
80 /ns_instances_content O O
92 /vnf_instances (also vnfrs for compatibility) O
108 /vim_accounts (also vims for compatibility) O O
120 /netslice_templates_content O O
122 /netslice_templates O O
126 /artifacts[/<artifactPath>] O
128 /<subscriptionId> X X
131 /netslice_instances_content O O
132 /<SliceInstanceId> O O
133 /netslice_instances O O
134 /<SliceInstanceId> O O
139 /<nsiLcmOpOccId> O O O
141 /<subscriptionId> X X
144 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
145 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
146 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
147 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
149 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
150 item of the array, that is, pass if any item of the array pass the filter.
151 It allows both ne and neq for not equal
152 TODO: 4.3.3 Attribute selectors
153 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
154 (none) … same as “exclude_default”
155 all_fields … all attributes.
156 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
157 conditionally mandatory, and that are not provided in <list>.
158 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
159 are not conditionally mandatory, and that are provided in <list>.
160 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
161 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
162 the particular resource
163 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
164 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
165 present specification for the particular resource, but that are not part of <list>
166 Additionally it admits some administrator values:
167 FORCE: To force operations skipping dependency checkings
168 ADMIN: To act as an administrator or a different project
169 PUBLIC: To get public descriptors or set a descriptor as public
170 SET_PROJECT: To make a descriptor available for other project
172 Header field name Reference Example Descriptions
173 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
174 This header field shall be present if the response is expected to have a non-empty message body.
175 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
176 This header field shall be present if the request has a non-empty message body.
177 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
178 Details are specified in clause 4.5.3.
179 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
180 Header field name Reference Example Descriptions
181 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
182 This header field shall be present if the response has a non-empty message body.
183 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
184 new resource has been created.
185 This header field shall be present if the response status code is 201 or 3xx.
186 In the present document this header field is also used if the response status code is 202 and a new resource was
188 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
189 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
191 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
193 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
194 response, and the total length of the file.
195 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
198 valid_query_string
= ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
199 # ^ Contains possible administrative query string words:
200 # ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
201 # (not owned by my session project).
202 # PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
203 # FORCE=True(by default)|False: Force edition/deletion operations
204 # SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
206 valid_url_methods
= {
207 # contains allowed URL and methods, and the role_permission name
210 "tokens": {"METHODS": ("GET", "POST", "DELETE"),
211 "ROLE_PERMISSION": "tokens:",
212 "<ID>": {"METHODS": ("GET", "DELETE"),
213 "ROLE_PERMISSION": "tokens:id:"
216 "users": {"METHODS": ("GET", "POST"),
217 "ROLE_PERMISSION": "users:",
218 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
219 "ROLE_PERMISSION": "users:id:"
222 "projects": {"METHODS": ("GET", "POST"),
223 "ROLE_PERMISSION": "projects:",
224 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
225 "ROLE_PERMISSION": "projects:id:"}
227 "roles": {"METHODS": ("GET", "POST"),
228 "ROLE_PERMISSION": "roles:",
229 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
230 "ROLE_PERMISSION": "roles:id:"
233 "vims": {"METHODS": ("GET", "POST"),
234 "ROLE_PERMISSION": "vims:",
235 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
236 "ROLE_PERMISSION": "vims:id:"
239 "vim_accounts": {"METHODS": ("GET", "POST"),
240 "ROLE_PERMISSION": "vim_accounts:",
241 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
242 "ROLE_PERMISSION": "vim_accounts:id:"
245 "wim_accounts": {"METHODS": ("GET", "POST"),
246 "ROLE_PERMISSION": "wim_accounts:",
247 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
248 "ROLE_PERMISSION": "wim_accounts:id:"
251 "sdns": {"METHODS": ("GET", "POST"),
252 "ROLE_PERMISSION": "sdn_controllers:",
253 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
254 "ROLE_PERMISSION": "sdn_controllers:id:"
257 "k8sclusters": {"METHODS": ("GET", "POST"),
258 "ROLE_PERMISSION": "k8sclusters:",
259 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
260 "ROLE_PERMISSION": "k8sclusters:id:"
263 "k8srepos": {"METHODS": ("GET", "POST"),
264 "ROLE_PERMISSION": "k8srepos:",
265 "<ID>": {"METHODS": ("GET", "DELETE"),
266 "ROLE_PERMISSION": "k8srepos:id:"
274 "pdu_descriptors": {"METHODS": ("GET", "POST"),
275 "ROLE_PERMISSION": "pduds:",
276 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
277 "ROLE_PERMISSION": "pduds:id:"
284 "ns_descriptors_content": {"METHODS": ("GET", "POST"),
285 "ROLE_PERMISSION": "nsds:",
286 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
287 "ROLE_PERMISSION": "nsds:id:"
290 "ns_descriptors": {"METHODS": ("GET", "POST"),
291 "ROLE_PERMISSION": "nsds:",
292 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
293 "ROLE_PERMISSION": "nsds:id:",
294 "nsd_content": {"METHODS": ("GET", "PUT"),
295 "ROLE_PERMISSION": "nsds:id:content:",
297 "nsd": {"METHODS": ("GET",), # descriptor inside package
298 "ROLE_PERMISSION": "nsds:id:content:"
300 "artifacts": {"*": {"METHODS": ("GET",),
301 "ROLE_PERMISSION": "nsds:id:nsd_artifact:"
306 "pnf_descriptors": {"TODO": ("GET", "POST"),
307 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
308 "pnfd_content": {"TODO": ("GET", "PUT")}
311 "subscriptions": {"TODO": ("GET", "POST"),
312 "<ID>": {"TODO": ("GET", "DELETE")}
318 "vnf_packages_content": {"METHODS": ("GET", "POST"),
319 "ROLE_PERMISSION": "vnfds:",
320 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
321 "ROLE_PERMISSION": "vnfds:id:"}
323 "vnf_packages": {"METHODS": ("GET", "POST"),
324 "ROLE_PERMISSION": "vnfds:",
325 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
326 "ROLE_PERMISSION": "vnfds:id:",
327 "package_content": {"METHODS": ("GET", "PUT"), # package
328 "ROLE_PERMISSION": "vnfds:id:",
329 "upload_from_uri": {"METHODS": (),
331 "ROLE_PERMISSION": "vnfds:id:upload:"
334 "vnfd": {"METHODS": ("GET", ), # descriptor inside package
335 "ROLE_PERMISSION": "vnfds:id:content:"
337 "artifacts": {"*": {"METHODS": ("GET", ),
338 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:"
343 "subscriptions": {"TODO": ("GET", "POST"),
344 "<ID>": {"TODO": ("GET", "DELETE")}
350 "ns_instances_content": {"METHODS": ("GET", "POST"),
351 "ROLE_PERMISSION": "ns_instances:",
352 "<ID>": {"METHODS": ("GET", "DELETE"),
353 "ROLE_PERMISSION": "ns_instances:id:"
356 "ns_instances": {"METHODS": ("GET", "POST"),
357 "ROLE_PERMISSION": "ns_instances:",
358 "<ID>": {"METHODS": ("GET", "DELETE"),
359 "ROLE_PERMISSION": "ns_instances:id:",
360 "scale": {"METHODS": ("POST",),
361 "ROLE_PERMISSION": "ns_instances:id:scale:"
363 "terminate": {"METHODS": ("POST",),
364 "ROLE_PERMISSION": "ns_instances:id:terminate:"
366 "instantiate": {"METHODS": ("POST",),
367 "ROLE_PERMISSION": "ns_instances:id:instantiate:"
369 "action": {"METHODS": ("POST",),
370 "ROLE_PERMISSION": "ns_instances:id:action:"
374 "ns_lcm_op_occs": {"METHODS": ("GET",),
375 "ROLE_PERMISSION": "ns_instances:opps:",
376 "<ID>": {"METHODS": ("GET",),
377 "ROLE_PERMISSION": "ns_instances:opps:id:"
380 "vnfrs": {"METHODS": ("GET",),
381 "ROLE_PERMISSION": "vnf_instances:",
382 "<ID>": {"METHODS": ("GET",),
383 "ROLE_PERMISSION": "vnf_instances:id:"
386 "vnf_instances": {"METHODS": ("GET",),
387 "ROLE_PERMISSION": "vnf_instances:",
388 "<ID>": {"METHODS": ("GET",),
389 "ROLE_PERMISSION": "vnf_instances:id:"
396 "netslice_templates_content": {"METHODS": ("GET", "POST"),
397 "ROLE_PERMISSION": "slice_templates:",
398 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
399 "ROLE_PERMISSION": "slice_templates:id:", }
401 "netslice_templates": {"METHODS": ("GET", "POST"),
402 "ROLE_PERMISSION": "slice_templates:",
403 "<ID>": {"METHODS": ("GET", "DELETE"),
405 "ROLE_PERMISSION": "slice_templates:id:",
406 "nst_content": {"METHODS": ("GET", "PUT"),
407 "ROLE_PERMISSION": "slice_templates:id:content:"
409 "nst": {"METHODS": ("GET",), # descriptor inside package
410 "ROLE_PERMISSION": "slice_templates:id:content:"
412 "artifacts": {"*": {"METHODS": ("GET",),
413 "ROLE_PERMISSION": "slice_templates:id:content:"
418 "subscriptions": {"TODO": ("GET", "POST"),
419 "<ID>": {"TODO": ("GET", "DELETE")}
425 "netslice_instances_content": {"METHODS": ("GET", "POST"),
426 "ROLE_PERMISSION": "slice_instances:",
427 "<ID>": {"METHODS": ("GET", "DELETE"),
428 "ROLE_PERMISSION": "slice_instances:id:"
431 "netslice_instances": {"METHODS": ("GET", "POST"),
432 "ROLE_PERMISSION": "slice_instances:",
433 "<ID>": {"METHODS": ("GET", "DELETE"),
434 "ROLE_PERMISSION": "slice_instances:id:",
435 "terminate": {"METHODS": ("POST",),
436 "ROLE_PERMISSION": "slice_instances:id:terminate:"
438 "instantiate": {"METHODS": ("POST",),
439 "ROLE_PERMISSION": "slice_instances:id:instantiate:"
441 "action": {"METHODS": ("POST",),
442 "ROLE_PERMISSION": "slice_instances:id:action:"
446 "nsi_lcm_op_occs": {"METHODS": ("GET",),
447 "ROLE_PERMISSION": "slice_instances:opps:",
448 "<ID>": {"METHODS": ("GET",),
449 "ROLE_PERMISSION": "slice_instances:opps:id:",
459 "<ID>": {"METHODS": ("GET",),
460 "ROLE_PERMISSION": "reports:id:",
470 class NbiException(Exception):
472 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
473 Exception.__init
__(self
, message
)
474 self
.http_code
= http_code
477 class Server(object):
479 # to decode bytes to str
480 reader
= getreader("utf-8")
484 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
485 self
.engine
= Engine(self
.authenticator
)
487 def _format_in(self
, kwargs
):
490 if cherrypy
.request
.body
.length
:
491 error_text
= "Invalid input format "
493 if "Content-Type" in cherrypy
.request
.headers
:
494 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
495 error_text
= "Invalid json format "
496 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
497 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
498 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
499 error_text
= "Invalid yaml format "
500 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
501 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
502 elif "application/binary" in cherrypy
.request
.headers
["Content-Type"] or \
503 "application/gzip" in cherrypy
.request
.headers
["Content-Type"] or \
504 "application/zip" in cherrypy
.request
.headers
["Content-Type"] or \
505 "text/plain" in cherrypy
.request
.headers
["Content-Type"]:
506 indata
= cherrypy
.request
.body
# .read()
507 elif "multipart/form-data" in cherrypy
.request
.headers
["Content-Type"]:
508 if "descriptor_file" in kwargs
:
509 filecontent
= kwargs
.pop("descriptor_file")
510 if not filecontent
.file:
511 raise NbiException("empty file or content", HTTPStatus
.BAD_REQUEST
)
512 indata
= filecontent
.file # .read()
513 if filecontent
.content_type
.value
:
514 cherrypy
.request
.headers
["Content-Type"] = filecontent
.content_type
.value
516 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
517 # "Only 'Content-Type' of type 'application/json' or
518 # 'application/yaml' for input format are available")
519 error_text
= "Invalid yaml format "
520 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
521 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
523 error_text
= "Invalid yaml format "
524 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
525 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
530 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
533 for k
, v
in kwargs
.items():
534 if isinstance(v
, str):
539 kwargs
[k
] = yaml
.load(v
, Loader
=yaml
.SafeLoader
)
542 elif k
.endswith(".gt") or k
.endswith(".lt") or k
.endswith(".gte") or k
.endswith(".lte"):
550 elif v
.find(",") > 0:
551 kwargs
[k
] = v
.split(",")
552 elif isinstance(v
, (list, tuple)):
553 for index
in range(0, len(v
)):
558 v
[index
] = yaml
.load(v
[index
], Loader
=yaml
.SafeLoader
)
563 except (ValueError, yaml
.YAMLError
) as exc
:
564 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
565 except KeyError as exc
:
566 raise NbiException("Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
)
567 except Exception as exc
:
568 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
571 def _format_out(data
, token_info
=None, _format
=None):
573 return string of dictionary data according to requested json, yaml, xml. By default json
574 :param data: response to be sent. Can be a dict, text or file
575 :param token_info: Contains among other username and project
576 :param _format: The format to be set as Content-Type if data is a file
579 accept
= cherrypy
.request
.headers
.get("Accept")
581 if accept
and "text/html" in accept
:
582 return html
.format(data
, cherrypy
.request
, cherrypy
.response
, token_info
)
583 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
585 elif hasattr(data
, "read"): # file object
587 cherrypy
.response
.headers
["Content-Type"] = _format
588 elif "b" in data
.mode
: # binariy asssumig zip
589 cherrypy
.response
.headers
["Content-Type"] = 'application/zip'
591 cherrypy
.response
.headers
["Content-Type"] = 'text/plain'
592 # TODO check that cherrypy close file. If not implement pending things to close per thread next
595 if "application/json" in accept
:
596 cherrypy
.response
.headers
["Content-Type"] = 'application/json; charset=utf-8'
597 a
= json
.dumps(data
, indent
=4) + "\n"
598 return a
.encode("utf8")
599 elif "text/html" in accept
:
600 return html
.format(data
, cherrypy
.request
, cherrypy
.response
, token_info
)
602 elif "application/yaml" in accept
or "*/*" in accept
or "text/plain" in accept
:
604 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
605 elif cherrypy
.response
.status
>= 400:
606 raise cherrypy
.HTTPError(HTTPStatus
.NOT_ACCEPTABLE
.value
,
607 "Only 'Accept' of type 'application/json' or 'application/yaml' "
608 "for output format are available")
609 cherrypy
.response
.headers
["Content-Type"] = 'application/yaml'
610 return yaml
.safe_dump(data
, explicit_start
=True, indent
=4, default_flow_style
=False, tags
=False,
611 encoding
='utf-8', allow_unicode
=True) # , canonical=True, default_style='"'
614 def index(self
, *args
, **kwargs
):
617 if cherrypy
.request
.method
== "GET":
618 token_info
= self
.authenticator
.authorize()
619 outdata
= token_info
# Home page
621 raise cherrypy
.HTTPError(HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
622 "Method {} not allowed for tokens".format(cherrypy
.request
.method
))
624 return self
._format
_out
(outdata
, token_info
)
626 except (EngineException
, AuthException
) as e
:
627 # cherrypy.log("index Exception {}".format(e))
628 cherrypy
.response
.status
= e
.http_code
.value
629 return self
._format
_out
("Welcome to OSM!", token_info
)
632 def version(self
, *args
, **kwargs
):
633 # TODO consider to remove and provide version using the static version file
635 if cherrypy
.request
.method
!= "GET":
636 raise NbiException("Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
)
638 raise NbiException("Invalid URL or query string for version", HTTPStatus
.METHOD_NOT_ALLOWED
)
639 # TODO include version of other modules, pick up from some kafka admin message
640 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
641 return self
._format
_out
(osm_nbi_version
)
642 except NbiException
as e
:
643 cherrypy
.response
.status
= e
.http_code
.value
645 "code": e
.http_code
.name
,
646 "status": e
.http_code
.value
,
649 return self
._format
_out
(problem_details
, None)
652 def _format_login(token_info
):
654 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
656 :param token_info: Dictionary with token content
659 cherrypy
.request
.login
= token_info
.get("username", "-")
660 if token_info
.get("project_name"):
661 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
662 if token_info
.get("id"):
663 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
666 def token(self
, method
, token_id
=None, kwargs
=None):
668 # self.engine.load_dbase(cherrypy.request.app.config)
669 indata
= self
._format
_in
(kwargs
)
670 if not isinstance(indata
, dict):
671 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus
.BAD_REQUEST
)
674 token_info
= self
.authenticator
.authorize()
676 self
._format
_login
(token_info
)
678 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
680 outdata
= self
.authenticator
.get_token_list(token_info
)
681 elif method
== "POST":
683 token_info
= self
.authenticator
.authorize()
687 indata
.update(kwargs
)
688 # This is needed to log the user when authentication fails
689 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
690 outdata
= token_info
= self
.authenticator
.new_token(token_info
, indata
, cherrypy
.request
.remote
)
691 cherrypy
.session
['Authorization'] = outdata
["_id"]
692 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
694 self
._format
_login
(token_info
)
696 # cherrypy.response.cookie["Authorization"] = outdata["id"]
697 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
698 elif method
== "DELETE":
699 if not token_id
and "id" in kwargs
:
700 token_id
= kwargs
["id"]
702 token_info
= self
.authenticator
.authorize()
704 self
._format
_login
(token_info
)
705 token_id
= token_info
["_id"]
706 outdata
= self
.authenticator
.del_token(token_id
)
708 cherrypy
.session
['Authorization'] = "logout"
709 # cherrypy.response.cookie["Authorization"] = token_id
710 # cherrypy.response.cookie["Authorization"]['expires'] = 0
712 raise NbiException("Method {} not allowed for token".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
713 return self
._format
_out
(outdata
, token_info
)
716 def test(self
, *args
, **kwargs
):
717 if not cherrypy
.config
.get("server.enable_test") or (isinstance(cherrypy
.config
["server.enable_test"], str) and
718 cherrypy
.config
["server.enable_test"].lower() == "false"):
719 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
720 return "test URL is disabled"
722 if args
and args
[0] == "help":
723 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
724 "sleep/<time>\nmessage/topic\n</pre></html>"
726 elif args
and args
[0] == "init":
728 # self.engine.load_dbase(cherrypy.request.app.config)
729 self
.engine
.create_admin()
730 return "Done. User 'admin', password 'admin' created"
732 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
733 return self
._format
_out
("Database already initialized")
734 elif args
and args
[0] == "file":
735 return cherrypy
.lib
.static
.serve_file(cherrypy
.tree
.apps
['/osm'].config
["storage"]["path"] + "/" + args
[1],
736 "text/plain", "attachment")
737 elif args
and args
[0] == "file2":
738 f_path
= cherrypy
.tree
.apps
['/osm'].config
["storage"]["path"] + "/" + args
[1]
739 f
= open(f_path
, "r")
740 cherrypy
.response
.headers
["Content-type"] = "text/plain"
743 elif len(args
) == 2 and args
[0] == "db-clear":
744 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
745 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
746 elif len(args
) and args
[0] == "fs-clear":
750 folders
= self
.engine
.fs
.dir_ls(".")
751 for folder
in folders
:
752 self
.engine
.fs
.file_delete(folder
)
753 return ",".join(folders
) + " folders deleted\n"
754 elif args
and args
[0] == "login":
755 if not cherrypy
.request
.headers
.get("Authorization"):
756 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
757 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
758 elif args
and args
[0] == "login2":
759 if not cherrypy
.request
.headers
.get("Authorization"):
760 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
761 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
762 elif args
and args
[0] == "sleep":
765 sleep_time
= int(args
[1])
767 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
768 return self
._format
_out
("Database already initialized")
769 thread_info
= cherrypy
.thread_data
771 time
.sleep(sleep_time
)
773 elif len(args
) >= 2 and args
[0] == "message":
775 return_text
= "<html><pre>{} ->\n".format(main_topic
)
777 if cherrypy
.request
.method
== 'POST':
778 to_send
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
779 for k
, v
in to_send
.items():
780 self
.engine
.msg
.write(main_topic
, k
, v
)
781 return_text
+= " {}: {}\n".format(k
, v
)
782 elif cherrypy
.request
.method
== 'GET':
783 for k
, v
in kwargs
.items():
784 self
.engine
.msg
.write(main_topic
, k
, yaml
.load(v
), Loader
=yaml
.SafeLoader
)
785 return_text
+= " {}: {}\n".format(k
, yaml
.load(v
), Loader
=yaml
.SafeLoader
)
786 except Exception as e
:
787 return_text
+= "Error: " + str(e
)
788 return_text
+= "</pre></html>\n"
792 "<html><pre>\nheaders:\n args: {}\n".format(args
) +
793 " kwargs: {}\n".format(kwargs
) +
794 " headers: {}\n".format(cherrypy
.request
.headers
) +
795 " path_info: {}\n".format(cherrypy
.request
.path_info
) +
796 " query_string: {}\n".format(cherrypy
.request
.query_string
) +
797 " session: {}\n".format(cherrypy
.session
) +
798 " cookie: {}\n".format(cherrypy
.request
.cookie
) +
799 " method: {}\n".format(cherrypy
.request
.method
) +
800 " session: {}\n".format(cherrypy
.session
.get('fieldname')) +
802 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
803 if cherrypy
.request
.body
.length
:
804 return_text
+= " content: {}\n".format(
805 str(cherrypy
.request
.body
.read(int(cherrypy
.request
.headers
.get('Content-Length', 0)))))
807 return_text
+= "thread: {}\n".format(thread_info
)
808 return_text
+= "</pre></html>"
812 def _check_valid_url_method(method
, *args
):
814 raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus
.METHOD_NOT_ALLOWED
)
816 reference
= valid_url_methods
820 if not isinstance(reference
, dict):
821 raise NbiException("URL contains unexpected extra items '{}'".format(arg
),
822 HTTPStatus
.METHOD_NOT_ALLOWED
)
825 reference
= reference
[arg
]
826 elif "<ID>" in reference
:
827 reference
= reference
["<ID>"]
828 elif "*" in reference
:
829 reference
= reference
["*"]
832 raise NbiException("Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
)
833 if "TODO" in reference
and method
in reference
["TODO"]:
834 raise NbiException("Method {} not supported yet for this URL".format(method
), HTTPStatus
.NOT_IMPLEMENTED
)
835 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
836 raise NbiException("Method {} not supported for this URL".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
837 return reference
["ROLE_PERMISSION"] + method
.lower()
840 def _set_location_header(main_topic
, version
, topic
, id):
842 Insert response header Location with the URL of created item base on URL params
849 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
850 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(main_topic
, version
, topic
, id)
854 def _extract_query_string_operations(kwargs
, method
):
860 query_string_operations
= []
862 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
863 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
864 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
865 return query_string_operations
868 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
870 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
871 Check that users has rights to use them and returs the admin_query
872 :param token_info: token_info rights obtained by token
873 :param kwargs: query string input.
874 :param method: http method: GET, POSST, PUT, ...
876 :return: admin_query dictionary with keys:
877 public: True, False or None
879 project_id: tuple with projects used for accessing an element
880 set_project: tuple with projects that a created element will belong to
881 method: show, list, delete, write
883 admin_query
= {"force": False, "project_id": (token_info
["project_id"], ), "username": token_info
["username"],
884 "admin": token_info
["admin"], "public": None,
885 "allow_show_user_project_role": token_info
["allow_show_user_project_role"]}
888 if "FORCE" in kwargs
:
889 if kwargs
["FORCE"].lower() != "false": # if None or True set force to True
890 admin_query
["force"] = True
893 if "PUBLIC" in kwargs
:
894 if kwargs
["PUBLIC"].lower() != "false": # if None or True set public to True
895 admin_query
["public"] = True
897 admin_query
["public"] = False
900 if "ADMIN" in kwargs
:
901 behave_as
= kwargs
.pop("ADMIN")
902 if behave_as
.lower() != "false":
903 if not token_info
["admin"]:
904 raise NbiException("Only admin projects can use 'ADMIN' query string", HTTPStatus
.UNAUTHORIZED
)
905 if not behave_as
or behave_as
.lower() == "true": # convert True, None to empty list
906 admin_query
["project_id"] = ()
907 elif isinstance(behave_as
, (list, tuple)):
908 admin_query
["project_id"] = behave_as
909 else: # isinstance(behave_as, str)
910 admin_query
["project_id"] = (behave_as
, )
911 if "SET_PROJECT" in kwargs
:
912 set_project
= kwargs
.pop("SET_PROJECT")
914 admin_query
["set_project"] = list(admin_query
["project_id"])
916 if isinstance(set_project
, str):
917 set_project
= (set_project
, )
918 if admin_query
["project_id"]:
919 for p
in set_project
:
920 if p
not in admin_query
["project_id"]:
921 raise NbiException("Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
922 "'ADMIN='{p}'".format(p
=p
), HTTPStatus
.UNAUTHORIZED
)
923 admin_query
["set_project"] = set_project
926 # if "PROJECT_READ" in kwargs:
927 # admin_query["project"] = kwargs.pop("project")
928 # if admin_query["project"] == token_info["project_id"]:
931 admin_query
["method"] = "show"
933 admin_query
["method"] = "list"
934 elif method
== "DELETE":
935 admin_query
["method"] = "delete"
937 admin_query
["method"] = "write"
941 def default(self
, main_topic
=None, version
=None, topic
=None, _id
=None, item
=None, *args
, **kwargs
):
948 engine_session
= None
950 if not main_topic
or not version
or not topic
:
951 raise NbiException("URL must contain at least 'main_topic/version/topic'",
952 HTTPStatus
.METHOD_NOT_ALLOWED
)
953 if main_topic
not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm", "nspm"):
954 raise NbiException("URL main_topic '{}' not supported".format(main_topic
),
955 HTTPStatus
.METHOD_NOT_ALLOWED
)
957 raise NbiException("URL version '{}' not supported".format(version
), HTTPStatus
.METHOD_NOT_ALLOWED
)
959 if kwargs
and "METHOD" in kwargs
and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
960 method
= kwargs
.pop("METHOD")
962 method
= cherrypy
.request
.method
964 role_permission
= self
._check
_valid
_url
_method
(method
, main_topic
, version
, topic
, _id
, item
, *args
)
965 query_string_operations
= self
._extract
_query
_string
_operations
(kwargs
, method
)
966 if main_topic
== "admin" and topic
== "tokens":
967 return self
.token(method
, _id
, kwargs
)
968 token_info
= self
.authenticator
.authorize(role_permission
, query_string_operations
, _id
)
969 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
970 indata
= self
._format
_in
(kwargs
)
972 if topic
== "subscriptions":
973 engine_topic
= main_topic
+ "_" + topic
974 if item
and topic
!= "pm_jobs":
977 if main_topic
== "nsd":
978 engine_topic
= "nsds"
979 elif main_topic
== "vnfpkgm":
980 engine_topic
= "vnfds"
981 elif main_topic
== "nslcm":
982 engine_topic
= "nsrs"
983 if topic
== "ns_lcm_op_occs":
984 engine_topic
= "nslcmops"
985 if topic
== "vnfrs" or topic
== "vnf_instances":
986 engine_topic
= "vnfrs"
987 elif main_topic
== "nst":
988 engine_topic
= "nsts"
989 elif main_topic
== "nsilcm":
990 engine_topic
= "nsis"
991 if topic
== "nsi_lcm_op_occs":
992 engine_topic
= "nsilcmops"
993 elif main_topic
== "pdu":
994 engine_topic
= "pdus"
995 if engine_topic
== "vims": # TODO this is for backward compatibility, it will be removed in the future
996 engine_topic
= "vim_accounts"
999 if item
in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
1000 if item
in ("vnfd", "nsd", "nst"):
1001 path
= "$DESCRIPTOR"
1004 elif item
== "artifacts":
1008 file, _format
= self
.engine
.get_file(engine_session
, engine_topic
, _id
, path
,
1009 cherrypy
.request
.headers
.get("Accept"))
1012 outdata
= self
.engine
.get_item_list(engine_session
, engine_topic
, kwargs
)
1014 if item
== "reports":
1015 # TODO check that project_id (_id in this context) has permissions
1017 outdata
= self
.engine
.get_item(engine_session
, engine_topic
, _id
)
1018 elif method
== "POST":
1019 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1020 if topic
in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"):
1021 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1023 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, {}, None,
1024 cherrypy
.request
.headers
)
1025 completed
= self
.engine
.upload_content(engine_session
, engine_topic
, _id
, indata
, kwargs
,
1026 cherrypy
.request
.headers
)
1028 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1030 cherrypy
.response
.headers
["Transaction-Id"] = _id
1031 outdata
= {"id": _id
}
1032 elif topic
== "ns_instances_content":
1034 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, indata
, kwargs
)
1036 indata
["lcmOperationType"] = "instantiate"
1037 indata
["nsInstanceId"] = _id
1038 nslcmop_id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nslcmops", indata
, None)
1039 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1040 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1041 elif topic
== "ns_instances" and item
:
1042 indata
["lcmOperationType"] = item
1043 indata
["nsInstanceId"] = _id
1044 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nslcmops", indata
, kwargs
)
1045 self
._set
_location
_header
(main_topic
, version
, "ns_lcm_op_occs", _id
)
1046 outdata
= {"id": _id
}
1047 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1048 elif topic
== "netslice_instances_content":
1049 # creates NetSlice_Instance_record (NSIR)
1050 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, indata
, kwargs
)
1051 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1052 indata
["lcmOperationType"] = "instantiate"
1053 indata
["netsliceInstanceId"] = _id
1054 nsilcmop_id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nsilcmops", indata
, kwargs
)
1055 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1057 elif topic
== "netslice_instances" and item
:
1058 indata
["lcmOperationType"] = item
1059 indata
["netsliceInstanceId"] = _id
1060 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nsilcmops", indata
, kwargs
)
1061 self
._set
_location
_header
(main_topic
, version
, "nsi_lcm_op_occs", _id
)
1062 outdata
= {"id": _id
}
1063 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1065 _id
, op_id
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, indata
, kwargs
,
1066 cherrypy
.request
.headers
)
1067 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1068 outdata
= {"id": _id
}
1070 outdata
["op_id"] = op_id
1071 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1072 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1074 elif method
== "DELETE":
1076 outdata
= self
.engine
.del_item_list(engine_session
, engine_topic
, kwargs
)
1077 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1078 else: # len(args) > 1
1079 delete_in_process
= False
1080 if topic
== "ns_instances_content" and not engine_session
["force"]:
1082 "lcmOperationType": "terminate",
1083 "nsInstanceId": _id
,
1086 opp_id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nslcmops", nslcmop_desc
, None)
1088 delete_in_process
= True
1089 outdata
= {"_id": opp_id
}
1090 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1091 elif topic
== "netslice_instances_content" and not engine_session
["force"]:
1093 "lcmOperationType": "terminate",
1094 "netsliceInstanceId": _id
,
1097 opp_id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None)
1099 delete_in_process
= True
1100 outdata
= {"_id": opp_id
}
1101 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1102 if not delete_in_process
:
1103 self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1104 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1105 if engine_topic
in ("vim_accounts", "wim_accounts", "sdns", "k8sclusters", "k8srepos"):
1106 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1108 elif method
in ("PUT", "PATCH"):
1110 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1111 raise NbiException("Nothing to update. Provide payload and/or query string",
1112 HTTPStatus
.BAD_REQUEST
)
1113 if item
in ("nsd_content", "package_content", "nst_content") and method
== "PUT":
1114 completed
= self
.engine
.upload_content(engine_session
, engine_topic
, _id
, indata
, kwargs
,
1115 cherrypy
.request
.headers
)
1117 cherrypy
.response
.headers
["Transaction-Id"] = id
1119 op_id
= self
.engine
.edit_item(engine_session
, engine_topic
, _id
, indata
, kwargs
)
1122 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1123 outdata
= {"op_id": op_id
}
1125 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1128 raise NbiException("Method {} not allowed".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
1130 # if Role information changes, it is needed to reload the information of roles
1131 if topic
== "roles" and method
!= "GET":
1132 self
.authenticator
.load_operation_to_allowed_roles()
1134 if topic
== "projects" and method
== "DELETE" \
1135 or topic
in ["users", "roles"] and method
in ["PUT", "PATCH", "DELETE"]:
1136 self
.authenticator
.remove_token_from_cache()
1138 return self
._format
_out
(outdata
, token_info
, _format
)
1139 except Exception as e
:
1140 if isinstance(e
, (NbiException
, EngineException
, DbException
, FsException
, MsgException
, AuthException
,
1141 ValidationError
, AuthconnException
)):
1142 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1143 http_code_name
= e
.http_code
.name
1144 cherrypy
.log("Exception {}".format(e
))
1146 http_code_value
= cherrypy
.response
.status
= HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1147 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1148 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1149 if hasattr(outdata
, "close"): # is an open file
1153 for rollback_item
in rollback
:
1155 if rollback_item
.get("operation") == "set":
1156 self
.engine
.db
.set_one(rollback_item
["topic"], {"_id": rollback_item
["_id"]},
1157 rollback_item
["content"], fail_on_empty
=False)
1159 self
.engine
.db
.del_one(rollback_item
["topic"], {"_id": rollback_item
["_id"]},
1160 fail_on_empty
=False)
1161 except Exception as e2
:
1162 rollback_error_text
= "Rollback Exception {}: {}".format(rollback_item
, e2
)
1163 cherrypy
.log(rollback_error_text
)
1164 error_text
+= ". " + rollback_error_text
1165 # if isinstance(e, MsgException):
1166 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1167 # engine_topic[:-1], method, error_text)
1169 "code": http_code_name
,
1170 "status": http_code_value
,
1171 "detail": error_text
,
1173 return self
._format
_out
(problem_details
, token_info
)
1174 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1177 self
._format
_login
(token_info
)
1178 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1179 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1180 if outdata
.get(logging_id
):
1181 cherrypy
.request
.login
+= ";{}={}".format(logging_id
, outdata
[logging_id
][:36])
1184 def _start_service():
1186 Callback function called when cherrypy.engine starts
1187 Override configuration with env variables
1188 Set database, storage, message configuration
1189 Init database with admin/admin user password
1192 global subscription_thread
1193 cherrypy
.log
.error("Starting osm_nbi")
1194 # update general cherrypy configuration
1197 engine_config
= cherrypy
.tree
.apps
['/osm'].config
1198 for k
, v
in environ
.items():
1199 if not k
.startswith("OSMNBI_"):
1201 k1
, _
, k2
= k
[7:].lower().partition("_")
1205 # update static configuration
1206 if k
== 'OSMNBI_STATIC_DIR':
1207 engine_config
["/static"]['tools.staticdir.dir'] = v
1208 engine_config
["/static"]['tools.staticdir.on'] = True
1209 elif k
== 'OSMNBI_SOCKET_PORT' or k
== 'OSMNBI_SERVER_PORT':
1210 update_dict
['server.socket_port'] = int(v
)
1211 elif k
== 'OSMNBI_SOCKET_HOST' or k
== 'OSMNBI_SERVER_HOST':
1212 update_dict
['server.socket_host'] = v
1213 elif k1
in ("server", "test", "auth", "log"):
1214 update_dict
[k1
+ '.' + k2
] = v
1215 elif k1
in ("message", "database", "storage", "authentication"):
1216 # k2 = k2.replace('_', '.')
1217 if k2
in ("port", "db_port"):
1218 engine_config
[k1
][k2
] = int(v
)
1220 engine_config
[k1
][k2
] = v
1222 except ValueError as e
:
1223 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1224 except Exception as e
:
1225 cherrypy
.log
.warn("skipping environ '{}' on exception '{}'".format(k
, e
))
1228 cherrypy
.config
.update(update_dict
)
1229 engine_config
["global"].update(update_dict
)
1232 log_format_simple
= "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1233 log_formatter_simple
= logging
.Formatter(log_format_simple
, datefmt
='%Y-%m-%dT%H:%M:%S')
1234 logger_server
= logging
.getLogger("cherrypy.error")
1235 logger_access
= logging
.getLogger("cherrypy.access")
1236 logger_cherry
= logging
.getLogger("cherrypy")
1237 logger_nbi
= logging
.getLogger("nbi")
1239 if "log.file" in engine_config
["global"]:
1240 file_handler
= logging
.handlers
.RotatingFileHandler(engine_config
["global"]["log.file"],
1241 maxBytes
=100e6
, backupCount
=9, delay
=0)
1242 file_handler
.setFormatter(log_formatter_simple
)
1243 logger_cherry
.addHandler(file_handler
)
1244 logger_nbi
.addHandler(file_handler
)
1245 # log always to standard output
1246 for format_
, logger
in {"nbi.server %(filename)s:%(lineno)s": logger_server
,
1247 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1248 "%(name)s %(filename)s:%(lineno)s": logger_nbi
1250 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1251 log_formatter_cherry
= logging
.Formatter(log_format_cherry
, datefmt
='%Y-%m-%dT%H:%M:%S')
1252 str_handler
= logging
.StreamHandler()
1253 str_handler
.setFormatter(log_formatter_cherry
)
1254 logger
.addHandler(str_handler
)
1256 if engine_config
["global"].get("log.level"):
1257 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1258 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1260 # logging other modules
1261 for k1
, logname
in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
1262 engine_config
[k1
]["logger_name"] = logname
1263 logger_module
= logging
.getLogger(logname
)
1264 if "logfile" in engine_config
[k1
]:
1265 file_handler
= logging
.handlers
.RotatingFileHandler(engine_config
[k1
]["logfile"],
1266 maxBytes
=100e6
, backupCount
=9, delay
=0)
1267 file_handler
.setFormatter(log_formatter_simple
)
1268 logger_module
.addHandler(file_handler
)
1269 if "loglevel" in engine_config
[k1
]:
1270 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1271 # TODO add more entries, e.g.: storage
1272 cherrypy
.tree
.apps
['/osm'].root
.engine
.start(engine_config
)
1273 cherrypy
.tree
.apps
['/osm'].root
.authenticator
.start(engine_config
)
1274 cherrypy
.tree
.apps
['/osm'].root
.engine
.init_db(target_version
=database_version
)
1275 cherrypy
.tree
.apps
['/osm'].root
.authenticator
.init_db(target_version
=auth_database_version
)
1277 # start subscriptions thread:
1278 subscription_thread
= SubscriptionThread(config
=engine_config
, engine
=nbi_server
.engine
)
1279 subscription_thread
.start()
1280 # Do not capture except SubscriptionException
1282 backend
= engine_config
["authentication"]["backend"]
1283 cherrypy
.log
.error("Starting OSM NBI Version '{} {}' with '{}' authentication backend"
1284 .format(nbi_version
, nbi_version_date
, backend
))
1287 def _stop_service():
1289 Callback function called when cherrypy.engine stops
1290 TODO: Ending database connections.
1292 global subscription_thread
1293 if subscription_thread
:
1294 subscription_thread
.terminate()
1295 subscription_thread
= None
1296 cherrypy
.tree
.apps
['/osm'].root
.engine
.stop()
1297 cherrypy
.log
.error("Stopping osm_nbi")
1300 def nbi(config_file
):
1304 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1305 # 'tools.sessions.on': True,
1306 # 'tools.response_headers.on': True,
1307 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1310 # cherrypy.Server.ssl_module = 'builtin'
1311 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1312 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1313 # cherrypy.Server.thread_pool = 10
1314 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1316 # cherrypy.config.update({'tools.auth_basic.on': True,
1317 # 'tools.auth_basic.realm': 'localhost',
1318 # 'tools.auth_basic.checkpassword': validate_password})
1319 nbi_server
= Server()
1320 cherrypy
.engine
.subscribe('start', _start_service
)
1321 cherrypy
.engine
.subscribe('stop', _stop_service
)
1322 cherrypy
.quickstart(nbi_server
, '/osm', config_file
)
1326 print("""Usage: {} [options]
1327 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1328 -h|--help: shows this help
1329 """.format(sys
.argv
[0]))
1330 # --log-socket-host HOST: send logs to this host")
1331 # --log-socket-port PORT: send logs using this port (default: 9022)")
1334 if __name__
== '__main__':
1336 # load parameters and configuration
1337 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
1338 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1341 if o
in ("-h", "--help"):
1344 elif o
in ("-c", "--config"):
1346 # elif o == "--log-socket-port":
1347 # log_socket_port = a
1348 # elif o == "--log-socket-host":
1349 # log_socket_host = a
1350 # elif o == "--log-file":
1353 assert False, "Unhandled option"
1355 if not path
.isfile(config_file
):
1356 print("configuration file '{}' that not exist".format(config_file
), file=sys
.stderr
)
1359 for config_file
in (__file__
[:__file__
.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
1360 if path
.isfile(config_file
):
1363 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys
.stderr
)
1366 except getopt
.GetoptError
as e
:
1367 print(str(e
), file=sys
.stderr
)