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
122 /netslice_templates_content O O
124 /netslice_templates O O
128 /artifacts[/<artifactPath>] O
130 /<subscriptionId> X X
133 /netslice_instances_content O O
134 /<SliceInstanceId> O O
135 /netslice_instances O O
136 /<SliceInstanceId> O O
141 /<nsiLcmOpOccId> O O O
143 /<subscriptionId> X X
146 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
147 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
148 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
149 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
151 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
152 item of the array, that is, pass if any item of the array pass the filter.
153 It allows both ne and neq for not equal
154 TODO: 4.3.3 Attribute selectors
155 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
156 (none) … same as “exclude_default”
157 all_fields … all attributes.
158 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
159 conditionally mandatory, and that are not provided in <list>.
160 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
161 are not conditionally mandatory, and that are provided in <list>.
162 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
163 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
164 the particular resource
165 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
166 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
167 present specification for the particular resource, but that are not part of <list>
168 Additionally it admits some administrator values:
169 FORCE: To force operations skipping dependency checkings
170 ADMIN: To act as an administrator or a different project
171 PUBLIC: To get public descriptors or set a descriptor as public
172 SET_PROJECT: To make a descriptor available for other project
174 Header field name Reference Example Descriptions
175 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
176 This header field shall be present if the response is expected to have a non-empty message body.
177 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
178 This header field shall be present if the request has a non-empty message body.
179 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
180 Details are specified in clause 4.5.3.
181 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
182 Header field name Reference Example Descriptions
183 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
184 This header field shall be present if the response has a non-empty message body.
185 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
186 new resource has been created.
187 This header field shall be present if the response status code is 201 or 3xx.
188 In the present document this header field is also used if the response status code is 202 and a new resource was
190 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
191 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
193 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
195 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
196 response, and the total length of the file.
197 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
200 valid_query_string
= ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
201 # ^ Contains possible administrative query string words:
202 # ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
203 # (not owned by my session project).
204 # PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
205 # FORCE=True(by default)|False: Force edition/deletion operations
206 # SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
208 valid_url_methods
= {
209 # contains allowed URL and methods, and the role_permission name
213 "METHODS": ("GET", "POST", "DELETE"),
214 "ROLE_PERMISSION": "tokens:",
215 "<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"},
218 "METHODS": ("GET", "POST"),
219 "ROLE_PERMISSION": "users:",
221 "METHODS": ("GET", "DELETE", "PATCH"),
222 "ROLE_PERMISSION": "users:id:",
226 "METHODS": ("GET", "POST"),
227 "ROLE_PERMISSION": "projects:",
229 "METHODS": ("GET", "DELETE", "PATCH"),
230 "ROLE_PERMISSION": "projects:id:",
234 "METHODS": ("GET", "POST"),
235 "ROLE_PERMISSION": "roles:",
237 "METHODS": ("GET", "DELETE", "PATCH"),
238 "ROLE_PERMISSION": "roles:id:",
242 "METHODS": ("GET", "POST"),
243 "ROLE_PERMISSION": "vims:",
245 "METHODS": ("GET", "DELETE", "PATCH"),
246 "ROLE_PERMISSION": "vims:id:",
250 "METHODS": ("GET", "POST"),
251 "ROLE_PERMISSION": "vim_accounts:",
253 "METHODS": ("GET", "DELETE", "PATCH"),
254 "ROLE_PERMISSION": "vim_accounts:id:",
258 "METHODS": ("GET", "POST"),
259 "ROLE_PERMISSION": "wim_accounts:",
261 "METHODS": ("GET", "DELETE", "PATCH"),
262 "ROLE_PERMISSION": "wim_accounts:id:",
266 "METHODS": ("GET", "POST"),
267 "ROLE_PERMISSION": "sdn_controllers:",
269 "METHODS": ("GET", "DELETE", "PATCH"),
270 "ROLE_PERMISSION": "sdn_controllers:id:",
274 "METHODS": ("GET", "POST"),
275 "ROLE_PERMISSION": "k8sclusters:",
277 "METHODS": ("GET", "DELETE", "PATCH"),
278 "ROLE_PERMISSION": "k8sclusters:id:",
282 "METHODS": ("GET", "POST"),
283 "ROLE_PERMISSION": "vca:",
285 "METHODS": ("GET", "DELETE", "PATCH"),
286 "ROLE_PERMISSION": "vca:id:",
290 "METHODS": ("GET", "POST"),
291 "ROLE_PERMISSION": "k8srepos:",
293 "METHODS": ("GET", "DELETE"),
294 "ROLE_PERMISSION": "k8srepos:id:",
298 "METHODS": ("GET", "POST"),
299 "ROLE_PERMISSION": "osmrepos:",
301 "METHODS": ("GET", "DELETE", "PATCH"),
302 "ROLE_PERMISSION": "osmrepos:id:",
307 "ROLE_PERMISSION": "domains:",
314 "METHODS": ("GET", "POST"),
315 "ROLE_PERMISSION": "pduds:",
317 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
318 "ROLE_PERMISSION": "pduds:id:",
325 "ns_descriptors_content": {
326 "METHODS": ("GET", "POST"),
327 "ROLE_PERMISSION": "nsds:",
329 "METHODS": ("GET", "PUT", "DELETE"),
330 "ROLE_PERMISSION": "nsds:id:",
334 "METHODS": ("GET", "POST"),
335 "ROLE_PERMISSION": "nsds:",
337 "METHODS": ("GET", "DELETE", "PATCH"),
338 "ROLE_PERMISSION": "nsds:id:",
340 "METHODS": ("GET", "PUT"),
341 "ROLE_PERMISSION": "nsds:id:content:",
344 "METHODS": ("GET",), # descriptor inside package
345 "ROLE_PERMISSION": "nsds:id:content:",
349 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
355 "TODO": ("GET", "POST"),
357 "TODO": ("GET", "DELETE", "PATCH"),
358 "pnfd_content": {"TODO": ("GET", "PUT")},
362 "TODO": ("GET", "POST"),
363 "<ID>": {"TODO": ("GET", "DELETE")},
369 "vnf_packages_content": {
370 "METHODS": ("GET", "POST"),
371 "ROLE_PERMISSION": "vnfds:",
373 "METHODS": ("GET", "PUT", "DELETE"),
374 "ROLE_PERMISSION": "vnfds:id:",
378 "METHODS": ("GET", "POST"),
379 "ROLE_PERMISSION": "vnfds:",
381 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
382 "ROLE_PERMISSION": "vnfds:id:",
384 "METHODS": ("GET", "PUT"), # package
385 "ROLE_PERMISSION": "vnfds:id:",
389 "ROLE_PERMISSION": "vnfds:id:upload:",
393 "METHODS": ("GET",), # descriptor inside package
394 "ROLE_PERMISSION": "vnfds:id:content:",
398 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
402 "METHODS": ("POST",),
403 "ROLE_PERMISSION": "vnfds:id:action:",
408 "TODO": ("GET", "POST"),
409 "<ID>": {"TODO": ("GET", "DELETE")},
413 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
414 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"},
420 "ns_instances_content": {
421 "METHODS": ("GET", "POST"),
422 "ROLE_PERMISSION": "ns_instances:",
424 "METHODS": ("GET", "DELETE"),
425 "ROLE_PERMISSION": "ns_instances:id:",
429 "METHODS": ("GET", "POST"),
430 "ROLE_PERMISSION": "ns_instances:",
432 "METHODS": ("GET", "DELETE"),
433 "ROLE_PERMISSION": "ns_instances:id:",
435 "METHODS": ("POST",),
436 "ROLE_PERMISSION": "ns_instances:id:scale:",
439 "METHODS": ("POST",),
440 "ROLE_PERMISSION": "ns_instances:id:terminate:",
443 "METHODS": ("POST",),
444 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
447 "METHODS": ("POST",),
448 "ROLE_PERMISSION": "ns_instances:id:action:",
454 "ROLE_PERMISSION": "ns_instances:opps:",
457 "ROLE_PERMISSION": "ns_instances:opps:id:",
462 "ROLE_PERMISSION": "vnf_instances:",
463 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
467 "ROLE_PERMISSION": "vnf_instances:",
468 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
471 "METHODS": ("GET", "POST"),
472 "ROLE_PERMISSION": "ns_subscriptions:",
474 "METHODS": ("GET", "DELETE"),
475 "ROLE_PERMISSION": "ns_subscriptions:id:",
482 "netslice_templates_content": {
483 "METHODS": ("GET", "POST"),
484 "ROLE_PERMISSION": "slice_templates:",
486 "METHODS": ("GET", "PUT", "DELETE"),
487 "ROLE_PERMISSION": "slice_templates:id:",
490 "netslice_templates": {
491 "METHODS": ("GET", "POST"),
492 "ROLE_PERMISSION": "slice_templates:",
494 "METHODS": ("GET", "DELETE"),
496 "ROLE_PERMISSION": "slice_templates:id:",
498 "METHODS": ("GET", "PUT"),
499 "ROLE_PERMISSION": "slice_templates:id:content:",
502 "METHODS": ("GET",), # descriptor inside package
503 "ROLE_PERMISSION": "slice_templates:id:content:",
507 "ROLE_PERMISSION": "slice_templates:id:content:",
513 "TODO": ("GET", "POST"),
514 "<ID>": {"TODO": ("GET", "DELETE")},
520 "netslice_instances_content": {
521 "METHODS": ("GET", "POST"),
522 "ROLE_PERMISSION": "slice_instances:",
524 "METHODS": ("GET", "DELETE"),
525 "ROLE_PERMISSION": "slice_instances:id:",
528 "netslice_instances": {
529 "METHODS": ("GET", "POST"),
530 "ROLE_PERMISSION": "slice_instances:",
532 "METHODS": ("GET", "DELETE"),
533 "ROLE_PERMISSION": "slice_instances:id:",
535 "METHODS": ("POST",),
536 "ROLE_PERMISSION": "slice_instances:id:terminate:",
539 "METHODS": ("POST",),
540 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
543 "METHODS": ("POST",),
544 "ROLE_PERMISSION": "slice_instances:id:action:",
550 "ROLE_PERMISSION": "slice_instances:opps:",
553 "ROLE_PERMISSION": "slice_instances:opps:id:",
565 "ROLE_PERMISSION": "reports:id:",
575 class NbiException(Exception):
576 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
577 Exception.__init
__(self
, message
)
578 self
.http_code
= http_code
581 class Server(object):
583 # to decode bytes to str
584 reader
= getreader("utf-8")
588 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
589 self
.engine
= Engine(self
.authenticator
)
591 def _format_in(self
, kwargs
):
594 if cherrypy
.request
.body
.length
:
595 error_text
= "Invalid input format "
597 if "Content-Type" in cherrypy
.request
.headers
:
598 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
599 error_text
= "Invalid json format "
600 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
601 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
602 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
603 error_text
= "Invalid yaml format "
605 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
607 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
609 "application/binary" in cherrypy
.request
.headers
["Content-Type"]
610 or "application/gzip"
611 in cherrypy
.request
.headers
["Content-Type"]
612 or "application/zip" in cherrypy
.request
.headers
["Content-Type"]
613 or "text/plain" in cherrypy
.request
.headers
["Content-Type"]
615 indata
= cherrypy
.request
.body
# .read()
617 "multipart/form-data"
618 in cherrypy
.request
.headers
["Content-Type"]
620 if "descriptor_file" in kwargs
:
621 filecontent
= kwargs
.pop("descriptor_file")
622 if not filecontent
.file:
624 "empty file or content", HTTPStatus
.BAD_REQUEST
626 indata
= filecontent
.file # .read()
627 if filecontent
.content_type
.value
:
628 cherrypy
.request
.headers
[
630 ] = filecontent
.content_type
.value
632 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
633 # "Only 'Content-Type' of type 'application/json' or
634 # 'application/yaml' for input format are available")
635 error_text
= "Invalid yaml format "
637 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
639 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
641 error_text
= "Invalid yaml format "
642 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
643 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
648 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
651 for k
, v
in kwargs
.items():
652 if isinstance(v
, str):
657 kwargs
[k
] = yaml
.load(v
, Loader
=yaml
.SafeLoader
)
663 or k
.endswith(".gte")
664 or k
.endswith(".lte")
673 elif v
.find(",") > 0:
674 kwargs
[k
] = v
.split(",")
675 elif isinstance(v
, (list, tuple)):
676 for index
in range(0, len(v
)):
681 v
[index
] = yaml
.load(v
[index
], Loader
=yaml
.SafeLoader
)
686 except (ValueError, yaml
.YAMLError
) as exc
:
687 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
688 except KeyError as exc
:
690 "Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
692 except Exception as exc
:
693 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
696 def _format_out(data
, token_info
=None, _format
=None):
698 return string of dictionary data according to requested json, yaml, xml. By default json
699 :param data: response to be sent. Can be a dict, text or file
700 :param token_info: Contains among other username and project
701 :param _format: The format to be set as Content-Type if data is a file
704 accept
= cherrypy
.request
.headers
.get("Accept")
706 if accept
and "text/html" in accept
:
708 data
, cherrypy
.request
, cherrypy
.response
, token_info
710 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
712 elif hasattr(data
, "read"): # file object
714 cherrypy
.response
.headers
["Content-Type"] = _format
715 elif "b" in data
.mode
: # binariy asssumig zip
716 cherrypy
.response
.headers
["Content-Type"] = "application/zip"
718 cherrypy
.response
.headers
["Content-Type"] = "text/plain"
719 # TODO check that cherrypy close file. If not implement pending things to close per thread next
722 if "text/html" in accept
:
724 data
, cherrypy
.request
, cherrypy
.response
, token_info
726 elif "application/yaml" in accept
or "*/*" in accept
:
728 elif "application/json" in accept
or (
729 cherrypy
.response
.status
and cherrypy
.response
.status
>= 300
731 cherrypy
.response
.headers
[
733 ] = "application/json; charset=utf-8"
734 a
= json
.dumps(data
, indent
=4) + "\n"
735 return a
.encode("utf8")
736 cherrypy
.response
.headers
["Content-Type"] = "application/yaml"
737 return yaml
.safe_dump(
741 default_flow_style
=False,
745 ) # , canonical=True, default_style='"'
748 def index(self
, *args
, **kwargs
):
751 if cherrypy
.request
.method
== "GET":
752 token_info
= self
.authenticator
.authorize()
753 outdata
= token_info
# Home page
755 raise cherrypy
.HTTPError(
756 HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
757 "Method {} not allowed for tokens".format(cherrypy
.request
.method
),
760 return self
._format
_out
(outdata
, token_info
)
762 except (EngineException
, AuthException
) as e
:
763 # cherrypy.log("index Exception {}".format(e))
764 cherrypy
.response
.status
= e
.http_code
.value
765 return self
._format
_out
("Welcome to OSM!", token_info
)
768 def version(self
, *args
, **kwargs
):
769 # TODO consider to remove and provide version using the static version file
771 if cherrypy
.request
.method
!= "GET":
773 "Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
777 "Invalid URL or query string for version",
778 HTTPStatus
.METHOD_NOT_ALLOWED
,
780 # TODO include version of other modules, pick up from some kafka admin message
781 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
782 return self
._format
_out
(osm_nbi_version
)
783 except NbiException
as e
:
784 cherrypy
.response
.status
= e
.http_code
.value
786 "code": e
.http_code
.name
,
787 "status": e
.http_code
.value
,
790 return self
._format
_out
(problem_details
, None)
795 "user_domain_name": cherrypy
.tree
.apps
["/osm"]
796 .config
["authentication"]
797 .get("user_domain_name"),
798 "project_domain_name": cherrypy
.tree
.apps
["/osm"]
799 .config
["authentication"]
800 .get("project_domain_name"),
802 return self
._format
_out
(domains
)
803 except NbiException
as e
:
804 cherrypy
.response
.status
= e
.http_code
.value
806 "code": e
.http_code
.name
,
807 "status": e
.http_code
.value
,
810 return self
._format
_out
(problem_details
, None)
813 def _format_login(token_info
):
815 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
817 :param token_info: Dictionary with token content
820 cherrypy
.request
.login
= token_info
.get("username", "-")
821 if token_info
.get("project_name"):
822 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
823 if token_info
.get("id"):
824 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
827 def token(self
, method
, token_id
=None, kwargs
=None):
829 # self.engine.load_dbase(cherrypy.request.app.config)
830 indata
= self
._format
_in
(kwargs
)
831 if not isinstance(indata
, dict):
833 "Expected application/yaml or application/json Content-Type",
834 HTTPStatus
.BAD_REQUEST
,
838 token_info
= self
.authenticator
.authorize()
840 self
._format
_login
(token_info
)
842 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
844 outdata
= self
.authenticator
.get_token_list(token_info
)
845 elif method
== "POST":
847 token_info
= self
.authenticator
.authorize()
851 indata
.update(kwargs
)
852 # This is needed to log the user when authentication fails
853 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
854 outdata
= token_info
= self
.authenticator
.new_token(
855 token_info
, indata
, cherrypy
.request
.remote
857 cherrypy
.session
["Authorization"] = outdata
["_id"]
858 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
860 self
._format
_login
(token_info
)
862 # cherrypy.response.cookie["Authorization"] = outdata["id"]
863 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
864 elif method
== "DELETE":
865 if not token_id
and "id" in kwargs
:
866 token_id
= kwargs
["id"]
868 token_info
= self
.authenticator
.authorize()
870 self
._format
_login
(token_info
)
871 token_id
= token_info
["_id"]
872 outdata
= self
.authenticator
.del_token(token_id
)
874 cherrypy
.session
["Authorization"] = "logout"
875 # cherrypy.response.cookie["Authorization"] = token_id
876 # cherrypy.response.cookie["Authorization"]['expires'] = 0
879 "Method {} not allowed for token".format(method
),
880 HTTPStatus
.METHOD_NOT_ALLOWED
,
882 return self
._format
_out
(outdata
, token_info
)
885 def test(self
, *args
, **kwargs
):
886 if not cherrypy
.config
.get("server.enable_test") or (
887 isinstance(cherrypy
.config
["server.enable_test"], str)
888 and cherrypy
.config
["server.enable_test"].lower() == "false"
890 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
891 return "test URL is disabled"
893 if args
and args
[0] == "help":
895 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
896 "sleep/<time>\nmessage/topic\n</pre></html>"
899 elif args
and args
[0] == "init":
901 # self.engine.load_dbase(cherrypy.request.app.config)
902 self
.engine
.create_admin()
903 return "Done. User 'admin', password 'admin' created"
905 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
906 return self
._format
_out
("Database already initialized")
907 elif args
and args
[0] == "file":
908 return cherrypy
.lib
.static
.serve_file(
909 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1],
913 elif args
and args
[0] == "file2":
915 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1]
917 f
= open(f_path
, "r")
918 cherrypy
.response
.headers
["Content-type"] = "text/plain"
921 elif len(args
) == 2 and args
[0] == "db-clear":
922 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
923 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
924 elif len(args
) and args
[0] == "fs-clear":
928 folders
= self
.engine
.fs
.dir_ls(".")
929 for folder
in folders
:
930 self
.engine
.fs
.file_delete(folder
)
931 return ",".join(folders
) + " folders deleted\n"
932 elif args
and args
[0] == "login":
933 if not cherrypy
.request
.headers
.get("Authorization"):
934 cherrypy
.response
.headers
[
936 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
937 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
938 elif args
and args
[0] == "login2":
939 if not cherrypy
.request
.headers
.get("Authorization"):
940 cherrypy
.response
.headers
[
942 ] = 'Bearer realm="Access to OSM site"'
943 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
944 elif args
and args
[0] == "sleep":
947 sleep_time
= int(args
[1])
949 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
950 return self
._format
_out
("Database already initialized")
951 thread_info
= cherrypy
.thread_data
953 time
.sleep(sleep_time
)
955 elif len(args
) >= 2 and args
[0] == "message":
957 return_text
= "<html><pre>{} ->\n".format(main_topic
)
959 if cherrypy
.request
.method
== "POST":
960 to_send
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
961 for k
, v
in to_send
.items():
962 self
.engine
.msg
.write(main_topic
, k
, v
)
963 return_text
+= " {}: {}\n".format(k
, v
)
964 elif cherrypy
.request
.method
== "GET":
965 for k
, v
in kwargs
.items():
966 v_dict
= yaml
.load(v
, Loader
=yaml
.SafeLoader
)
967 self
.engine
.msg
.write(main_topic
, k
, v_dict
)
968 return_text
+= " {}: {}\n".format(k
, v_dict
)
969 except Exception as e
:
970 return_text
+= "Error: " + str(e
)
971 return_text
+= "</pre></html>\n"
975 "<html><pre>\nheaders:\n args: {}\n".format(args
)
976 + " kwargs: {}\n".format(kwargs
)
977 + " headers: {}\n".format(cherrypy
.request
.headers
)
978 + " path_info: {}\n".format(cherrypy
.request
.path_info
)
979 + " query_string: {}\n".format(cherrypy
.request
.query_string
)
980 + " session: {}\n".format(cherrypy
.session
)
981 + " cookie: {}\n".format(cherrypy
.request
.cookie
)
982 + " method: {}\n".format(cherrypy
.request
.method
)
983 + " session: {}\n".format(cherrypy
.session
.get("fieldname"))
986 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
987 if cherrypy
.request
.body
.length
:
988 return_text
+= " content: {}\n".format(
990 cherrypy
.request
.body
.read(
991 int(cherrypy
.request
.headers
.get("Content-Length", 0))
996 return_text
+= "thread: {}\n".format(thread_info
)
997 return_text
+= "</pre></html>"
1001 def _check_valid_url_method(method
, *args
):
1004 "URL must contain at least 'main_topic/version/topic'",
1005 HTTPStatus
.METHOD_NOT_ALLOWED
,
1008 reference
= valid_url_methods
1012 if not isinstance(reference
, dict):
1014 "URL contains unexpected extra items '{}'".format(arg
),
1015 HTTPStatus
.METHOD_NOT_ALLOWED
,
1018 if arg
in reference
:
1019 reference
= reference
[arg
]
1020 elif "<ID>" in reference
:
1021 reference
= reference
["<ID>"]
1022 elif "*" in reference
:
1023 # if there is content
1025 reference
= reference
["*"]
1029 "Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
1031 if "TODO" in reference
and method
in reference
["TODO"]:
1033 "Method {} not supported yet for this URL".format(method
),
1034 HTTPStatus
.NOT_IMPLEMENTED
,
1036 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
1038 "Method {} not supported for this URL".format(method
),
1039 HTTPStatus
.METHOD_NOT_ALLOWED
,
1041 return reference
["ROLE_PERMISSION"] + method
.lower()
1044 def _set_location_header(main_topic
, version
, topic
, id):
1046 Insert response header Location with the URL of created item base on URL params
1053 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1054 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(
1055 main_topic
, version
, topic
, id
1060 def _extract_query_string_operations(kwargs
, method
):
1066 query_string_operations
= []
1068 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1069 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
1070 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
1071 return query_string_operations
1074 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
1076 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1077 Check that users has rights to use them and returs the admin_query
1078 :param token_info: token_info rights obtained by token
1079 :param kwargs: query string input.
1080 :param method: http method: GET, POSST, PUT, ...
1082 :return: admin_query dictionary with keys:
1083 public: True, False or None
1084 force: True or False
1085 project_id: tuple with projects used for accessing an element
1086 set_project: tuple with projects that a created element will belong to
1087 method: show, list, delete, write
1091 "project_id": (token_info
["project_id"],),
1092 "username": token_info
["username"],
1093 "admin": token_info
["admin"],
1095 "allow_show_user_project_role": token_info
["allow_show_user_project_role"],
1099 if "FORCE" in kwargs
:
1101 kwargs
["FORCE"].lower() != "false"
1102 ): # if None or True set force to True
1103 admin_query
["force"] = True
1106 if "PUBLIC" in kwargs
:
1108 kwargs
["PUBLIC"].lower() != "false"
1109 ): # if None or True set public to True
1110 admin_query
["public"] = True
1112 admin_query
["public"] = False
1113 del kwargs
["PUBLIC"]
1115 if "ADMIN" in kwargs
:
1116 behave_as
= kwargs
.pop("ADMIN")
1117 if behave_as
.lower() != "false":
1118 if not token_info
["admin"]:
1120 "Only admin projects can use 'ADMIN' query string",
1121 HTTPStatus
.UNAUTHORIZED
,
1124 not behave_as
or behave_as
.lower() == "true"
1125 ): # convert True, None to empty list
1126 admin_query
["project_id"] = ()
1127 elif isinstance(behave_as
, (list, tuple)):
1128 admin_query
["project_id"] = behave_as
1129 else: # isinstance(behave_as, str)
1130 admin_query
["project_id"] = (behave_as
,)
1131 if "SET_PROJECT" in kwargs
:
1132 set_project
= kwargs
.pop("SET_PROJECT")
1134 admin_query
["set_project"] = list(admin_query
["project_id"])
1136 if isinstance(set_project
, str):
1137 set_project
= (set_project
,)
1138 if admin_query
["project_id"]:
1139 for p
in set_project
:
1140 if p
not in admin_query
["project_id"]:
1142 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1143 "'ADMIN='{p}'".format(p
=p
),
1144 HTTPStatus
.UNAUTHORIZED
,
1146 admin_query
["set_project"] = set_project
1149 # if "PROJECT_READ" in kwargs:
1150 # admin_query["project"] = kwargs.pop("project")
1151 # if admin_query["project"] == token_info["project_id"]:
1154 admin_query
["method"] = "show"
1156 admin_query
["method"] = "list"
1157 elif method
== "DELETE":
1158 admin_query
["method"] = "delete"
1160 admin_query
["method"] = "write"
1180 engine_session
= None
1182 if not main_topic
or not version
or not topic
:
1184 "URL must contain at least 'main_topic/version/topic'",
1185 HTTPStatus
.METHOD_NOT_ALLOWED
,
1187 if main_topic
not in (
1198 "URL main_topic '{}' not supported".format(main_topic
),
1199 HTTPStatus
.METHOD_NOT_ALLOWED
,
1203 "URL version '{}' not supported".format(version
),
1204 HTTPStatus
.METHOD_NOT_ALLOWED
,
1209 and "METHOD" in kwargs
1210 and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1212 method
= kwargs
.pop("METHOD")
1214 method
= cherrypy
.request
.method
1216 role_permission
= self
._check
_valid
_url
_method
(
1217 method
, main_topic
, version
, topic
, _id
, item
, *args
1219 query_string_operations
= self
._extract
_query
_string
_operations
(
1222 if main_topic
== "admin" and topic
== "tokens":
1223 return self
.token(method
, _id
, kwargs
)
1224 token_info
= self
.authenticator
.authorize(
1225 role_permission
, query_string_operations
, _id
1227 if main_topic
== "admin" and topic
== "domains":
1228 return self
.domain()
1229 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
1230 indata
= self
._format
_in
(kwargs
)
1231 engine_topic
= topic
1233 if item
and topic
!= "pm_jobs":
1236 if main_topic
== "nsd":
1237 engine_topic
= "nsds"
1238 elif main_topic
== "vnfpkgm":
1239 engine_topic
= "vnfds"
1240 if topic
== "vnfpkg_op_occs":
1241 engine_topic
= "vnfpkgops"
1242 if topic
== "vnf_packages" and item
== "action":
1243 engine_topic
= "vnfpkgops"
1244 elif main_topic
== "nslcm":
1245 engine_topic
= "nsrs"
1246 if topic
== "ns_lcm_op_occs":
1247 engine_topic
= "nslcmops"
1248 if topic
== "vnfrs" or topic
== "vnf_instances":
1249 engine_topic
= "vnfrs"
1250 elif main_topic
== "nst":
1251 engine_topic
= "nsts"
1252 elif main_topic
== "nsilcm":
1253 engine_topic
= "nsis"
1254 if topic
== "nsi_lcm_op_occs":
1255 engine_topic
= "nsilcmops"
1256 elif main_topic
== "pdu":
1257 engine_topic
= "pdus"
1259 engine_topic
== "vims"
1260 ): # TODO this is for backward compatibility, it will be removed in the future
1261 engine_topic
= "vim_accounts"
1263 if topic
== "subscriptions":
1264 engine_topic
= main_topic
+ "_" + topic
1276 if item
in ("vnfd", "nsd", "nst"):
1277 path
= "$DESCRIPTOR"
1280 elif item
== "artifacts":
1284 file, _format
= self
.engine
.get_file(
1289 cherrypy
.request
.headers
.get("Accept"),
1293 outdata
= self
.engine
.get_item_list(
1294 engine_session
, engine_topic
, kwargs
, api_req
=True
1297 if item
== "reports":
1298 # TODO check that project_id (_id in this context) has permissions
1301 if "vcaStatusRefresh" in kwargs
:
1302 filter_q
= {"vcaStatusRefresh": kwargs
["vcaStatusRefresh"]}
1303 outdata
= self
.engine
.get_item(engine_session
, engine_topic
, _id
, filter_q
, True)
1305 elif method
== "POST":
1306 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1308 "ns_descriptors_content",
1309 "vnf_packages_content",
1310 "netslice_templates_content",
1312 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1314 _id
, _
= self
.engine
.new_item(
1320 cherrypy
.request
.headers
,
1322 completed
= self
.engine
.upload_content(
1328 cherrypy
.request
.headers
,
1331 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1333 cherrypy
.response
.headers
["Transaction-Id"] = _id
1334 outdata
= {"id": _id
}
1335 elif topic
== "ns_instances_content":
1337 _id
, _
= self
.engine
.new_item(
1338 rollback
, engine_session
, engine_topic
, indata
, kwargs
1341 indata
["lcmOperationType"] = "instantiate"
1342 indata
["nsInstanceId"] = _id
1343 nslcmop_id
, _
= self
.engine
.new_item(
1344 rollback
, engine_session
, "nslcmops", indata
, None
1346 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1347 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1348 elif topic
== "ns_instances" and item
:
1349 indata
["lcmOperationType"] = item
1350 indata
["nsInstanceId"] = _id
1351 _id
, _
= self
.engine
.new_item(
1352 rollback
, engine_session
, "nslcmops", indata
, kwargs
1354 self
._set
_location
_header
(
1355 main_topic
, version
, "ns_lcm_op_occs", _id
1357 outdata
= {"id": _id
}
1358 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1359 elif topic
== "netslice_instances_content":
1360 # creates NetSlice_Instance_record (NSIR)
1361 _id
, _
= self
.engine
.new_item(
1362 rollback
, engine_session
, engine_topic
, indata
, kwargs
1364 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1365 indata
["lcmOperationType"] = "instantiate"
1366 indata
["netsliceInstanceId"] = _id
1367 nsilcmop_id
, _
= self
.engine
.new_item(
1368 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1370 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1371 elif topic
== "netslice_instances" and item
:
1372 indata
["lcmOperationType"] = item
1373 indata
["netsliceInstanceId"] = _id
1374 _id
, _
= self
.engine
.new_item(
1375 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1377 self
._set
_location
_header
(
1378 main_topic
, version
, "nsi_lcm_op_occs", _id
1380 outdata
= {"id": _id
}
1381 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1382 elif topic
== "vnf_packages" and item
== "action":
1383 indata
["lcmOperationType"] = item
1384 indata
["vnfPkgId"] = _id
1385 _id
, _
= self
.engine
.new_item(
1386 rollback
, engine_session
, "vnfpkgops", indata
, kwargs
1388 self
._set
_location
_header
(
1389 main_topic
, version
, "vnfpkg_op_occs", _id
1391 outdata
= {"id": _id
}
1392 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1393 elif topic
== "subscriptions":
1394 _id
, _
= self
.engine
.new_item(
1395 rollback
, engine_session
, engine_topic
, indata
, kwargs
1397 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1399 link
["self"] = cherrypy
.response
.headers
["Location"]
1402 "filter": indata
["filter"],
1403 "callbackUri": indata
["CallbackUri"],
1406 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1408 _id
, op_id
= self
.engine
.new_item(
1414 cherrypy
.request
.headers
,
1416 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1417 outdata
= {"id": _id
}
1419 outdata
["op_id"] = op_id
1420 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1421 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1423 elif method
== "DELETE":
1425 outdata
= self
.engine
.del_item_list(
1426 engine_session
, engine_topic
, kwargs
1428 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1429 else: # len(args) > 1
1430 # for NS NSI generate an operation
1432 if topic
== "ns_instances_content" and not engine_session
["force"]:
1434 "lcmOperationType": "terminate",
1435 "nsInstanceId": _id
,
1438 op_id
, _
= self
.engine
.new_item(
1439 rollback
, engine_session
, "nslcmops", nslcmop_desc
, kwargs
1442 outdata
= {"_id": op_id
}
1444 topic
== "netslice_instances_content"
1445 and not engine_session
["force"]
1448 "lcmOperationType": "terminate",
1449 "netsliceInstanceId": _id
,
1452 op_id
, _
= self
.engine
.new_item(
1453 rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None
1456 outdata
= {"_id": op_id
}
1457 # if there is not any deletion in process, delete
1459 op_id
= self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1461 outdata
= {"op_id": op_id
}
1462 cherrypy
.response
.status
= (
1463 HTTPStatus
.ACCEPTED
.value
1465 else HTTPStatus
.NO_CONTENT
.value
1468 elif method
in ("PUT", "PATCH"):
1470 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1472 "Nothing to update. Provide payload and/or query string",
1473 HTTPStatus
.BAD_REQUEST
,
1476 item
in ("nsd_content", "package_content", "nst_content")
1479 completed
= self
.engine
.upload_content(
1485 cherrypy
.request
.headers
,
1488 cherrypy
.response
.headers
["Transaction-Id"] = id
1490 op_id
= self
.engine
.edit_item(
1491 engine_session
, engine_topic
, _id
, indata
, kwargs
1495 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1496 outdata
= {"op_id": op_id
}
1498 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1502 "Method {} not allowed".format(method
),
1503 HTTPStatus
.METHOD_NOT_ALLOWED
,
1506 # if Role information changes, it is needed to reload the information of roles
1507 if topic
== "roles" and method
!= "GET":
1508 self
.authenticator
.load_operation_to_allowed_roles()
1512 and method
== "DELETE"
1513 or topic
in ["users", "roles"]
1514 and method
in ["PUT", "PATCH", "DELETE"]
1516 self
.authenticator
.remove_token_from_cache()
1518 return self
._format
_out
(outdata
, token_info
, _format
)
1519 except Exception as e
:
1533 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1534 http_code_name
= e
.http_code
.name
1535 cherrypy
.log("Exception {}".format(e
))
1538 cherrypy
.response
.status
1539 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1540 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1541 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1542 if hasattr(outdata
, "close"): # is an open file
1546 for rollback_item
in rollback
:
1548 if rollback_item
.get("operation") == "set":
1549 self
.engine
.db
.set_one(
1550 rollback_item
["topic"],
1551 {"_id": rollback_item
["_id"]},
1552 rollback_item
["content"],
1553 fail_on_empty
=False,
1555 elif rollback_item
.get("operation") == "del_list":
1556 self
.engine
.db
.del_list(
1557 rollback_item
["topic"],
1558 rollback_item
["filter"],
1559 fail_on_empty
=False,
1562 self
.engine
.db
.del_one(
1563 rollback_item
["topic"],
1564 {"_id": rollback_item
["_id"]},
1565 fail_on_empty
=False,
1567 except Exception as e2
:
1568 rollback_error_text
= "Rollback Exception {}: {}".format(
1571 cherrypy
.log(rollback_error_text
)
1572 error_text
+= ". " + rollback_error_text
1573 # if isinstance(e, MsgException):
1574 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1575 # engine_topic[:-1], method, error_text)
1577 "code": http_code_name
,
1578 "status": http_code_value
,
1579 "detail": error_text
,
1581 return self
._format
_out
(problem_details
, token_info
)
1582 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1585 self
._format
_login
(token_info
)
1586 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1587 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1588 if outdata
.get(logging_id
):
1589 cherrypy
.request
.login
+= ";{}={}".format(
1590 logging_id
, outdata
[logging_id
][:36]
1594 def _start_service():
1596 Callback function called when cherrypy.engine starts
1597 Override configuration with env variables
1598 Set database, storage, message configuration
1599 Init database with admin/admin user password
1602 global subscription_thread
1603 cherrypy
.log
.error("Starting osm_nbi")
1604 # update general cherrypy configuration
1607 engine_config
= cherrypy
.tree
.apps
["/osm"].config
1608 for k
, v
in environ
.items():
1609 if not k
.startswith("OSMNBI_"):
1611 k1
, _
, k2
= k
[7:].lower().partition("_")
1615 # update static configuration
1616 if k
== "OSMNBI_STATIC_DIR":
1617 engine_config
["/static"]["tools.staticdir.dir"] = v
1618 engine_config
["/static"]["tools.staticdir.on"] = True
1619 elif k
== "OSMNBI_SOCKET_PORT" or k
== "OSMNBI_SERVER_PORT":
1620 update_dict
["server.socket_port"] = int(v
)
1621 elif k
== "OSMNBI_SOCKET_HOST" or k
== "OSMNBI_SERVER_HOST":
1622 update_dict
["server.socket_host"] = v
1623 elif k1
in ("server", "test", "auth", "log"):
1624 update_dict
[k1
+ "." + k2
] = v
1625 elif k1
in ("message", "database", "storage", "authentication"):
1626 # k2 = k2.replace('_', '.')
1627 if k2
in ("port", "db_port"):
1628 engine_config
[k1
][k2
] = int(v
)
1630 engine_config
[k1
][k2
] = v
1632 except ValueError as e
:
1633 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1634 except Exception as e
:
1635 cherrypy
.log
.warn("skipping environ '{}' on exception '{}'".format(k
, e
))
1638 cherrypy
.config
.update(update_dict
)
1639 engine_config
["global"].update(update_dict
)
1642 log_format_simple
= (
1643 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1645 log_formatter_simple
= logging
.Formatter(
1646 log_format_simple
, datefmt
="%Y-%m-%dT%H:%M:%S"
1648 logger_server
= logging
.getLogger("cherrypy.error")
1649 logger_access
= logging
.getLogger("cherrypy.access")
1650 logger_cherry
= logging
.getLogger("cherrypy")
1651 logger_nbi
= logging
.getLogger("nbi")
1653 if "log.file" in engine_config
["global"]:
1654 file_handler
= logging
.handlers
.RotatingFileHandler(
1655 engine_config
["global"]["log.file"], maxBytes
=100e6
, backupCount
=9, delay
=0
1657 file_handler
.setFormatter(log_formatter_simple
)
1658 logger_cherry
.addHandler(file_handler
)
1659 logger_nbi
.addHandler(file_handler
)
1660 # log always to standard output
1661 for format_
, logger
in {
1662 "nbi.server %(filename)s:%(lineno)s": logger_server
,
1663 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1664 "%(name)s %(filename)s:%(lineno)s": logger_nbi
,
1666 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1667 log_formatter_cherry
= logging
.Formatter(
1668 log_format_cherry
, datefmt
="%Y-%m-%dT%H:%M:%S"
1670 str_handler
= logging
.StreamHandler()
1671 str_handler
.setFormatter(log_formatter_cherry
)
1672 logger
.addHandler(str_handler
)
1674 if engine_config
["global"].get("log.level"):
1675 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1676 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1678 # logging other modules
1679 for k1
, logname
in {
1680 "message": "nbi.msg",
1681 "database": "nbi.db",
1682 "storage": "nbi.fs",
1684 engine_config
[k1
]["logger_name"] = logname
1685 logger_module
= logging
.getLogger(logname
)
1686 if "logfile" in engine_config
[k1
]:
1687 file_handler
= logging
.handlers
.RotatingFileHandler(
1688 engine_config
[k1
]["logfile"], maxBytes
=100e6
, backupCount
=9, delay
=0
1690 file_handler
.setFormatter(log_formatter_simple
)
1691 logger_module
.addHandler(file_handler
)
1692 if "loglevel" in engine_config
[k1
]:
1693 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1694 # TODO add more entries, e.g.: storage
1695 cherrypy
.tree
.apps
["/osm"].root
.engine
.start(engine_config
)
1696 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.start(engine_config
)
1697 cherrypy
.tree
.apps
["/osm"].root
.engine
.init_db(target_version
=database_version
)
1698 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.init_db(
1699 target_version
=auth_database_version
1702 # start subscriptions thread:
1703 subscription_thread
= SubscriptionThread(
1704 config
=engine_config
, engine
=nbi_server
.engine
1706 subscription_thread
.start()
1707 # Do not capture except SubscriptionException
1709 backend
= engine_config
["authentication"]["backend"]
1711 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1712 nbi_version
, nbi_version_date
, backend
1717 def _stop_service():
1719 Callback function called when cherrypy.engine stops
1720 TODO: Ending database connections.
1722 global subscription_thread
1723 if subscription_thread
:
1724 subscription_thread
.terminate()
1725 subscription_thread
= None
1726 cherrypy
.tree
.apps
["/osm"].root
.engine
.stop()
1727 cherrypy
.log
.error("Stopping osm_nbi")
1730 def nbi(config_file
):
1734 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1735 # 'tools.sessions.on': True,
1736 # 'tools.response_headers.on': True,
1737 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1740 # cherrypy.Server.ssl_module = 'builtin'
1741 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1742 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1743 # cherrypy.Server.thread_pool = 10
1744 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1746 # cherrypy.config.update({'tools.auth_basic.on': True,
1747 # 'tools.auth_basic.realm': 'localhost',
1748 # 'tools.auth_basic.checkpassword': validate_password})
1749 nbi_server
= Server()
1750 cherrypy
.engine
.subscribe("start", _start_service
)
1751 cherrypy
.engine
.subscribe("stop", _stop_service
)
1752 cherrypy
.quickstart(nbi_server
, "/osm", config_file
)
1757 """Usage: {} [options]
1758 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1759 -h|--help: shows this help
1764 # --log-socket-host HOST: send logs to this host")
1765 # --log-socket-port PORT: send logs using this port (default: 9022)")
1768 if __name__
== "__main__":
1770 # load parameters and configuration
1771 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
1772 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1775 if o
in ("-h", "--help"):
1778 elif o
in ("-c", "--config"):
1780 # elif o == "--log-socket-port":
1781 # log_socket_port = a
1782 # elif o == "--log-socket-host":
1783 # log_socket_host = a
1784 # elif o == "--log-file":
1787 assert False, "Unhandled option"
1789 if not path
.isfile(config_file
):
1791 "configuration file '{}' that not exist".format(config_file
),
1796 for config_file
in (
1797 __file__
[: __file__
.rfind(".")] + ".cfg",
1801 if path
.isfile(config_file
):
1805 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
1810 except getopt
.GetoptError
as e
:
1811 print(str(e
), file=sys
.stderr
)