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:"
341 "action": {"METHODS": ("POST", ),
342 "ROLE_PERMISSION": "vnfds:id:action:"
346 "subscriptions": {"TODO": ("GET", "POST"),
347 "<ID>": {"TODO": ("GET", "DELETE")}
349 "vnfpkg_op_occs": {"METHODS": ("GET", ),
350 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
351 "<ID>": {"METHODS": ("GET", ),
352 "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"
359 "ns_instances_content": {"METHODS": ("GET", "POST"),
360 "ROLE_PERMISSION": "ns_instances:",
361 "<ID>": {"METHODS": ("GET", "DELETE"),
362 "ROLE_PERMISSION": "ns_instances:id:"
365 "ns_instances": {"METHODS": ("GET", "POST"),
366 "ROLE_PERMISSION": "ns_instances:",
367 "<ID>": {"METHODS": ("GET", "DELETE"),
368 "ROLE_PERMISSION": "ns_instances:id:",
369 "scale": {"METHODS": ("POST",),
370 "ROLE_PERMISSION": "ns_instances:id:scale:"
372 "terminate": {"METHODS": ("POST",),
373 "ROLE_PERMISSION": "ns_instances:id:terminate:"
375 "instantiate": {"METHODS": ("POST",),
376 "ROLE_PERMISSION": "ns_instances:id:instantiate:"
378 "action": {"METHODS": ("POST",),
379 "ROLE_PERMISSION": "ns_instances:id:action:"
383 "ns_lcm_op_occs": {"METHODS": ("GET",),
384 "ROLE_PERMISSION": "ns_instances:opps:",
385 "<ID>": {"METHODS": ("GET",),
386 "ROLE_PERMISSION": "ns_instances:opps:id:"
389 "vnfrs": {"METHODS": ("GET",),
390 "ROLE_PERMISSION": "vnf_instances:",
391 "<ID>": {"METHODS": ("GET",),
392 "ROLE_PERMISSION": "vnf_instances:id:"
395 "vnf_instances": {"METHODS": ("GET",),
396 "ROLE_PERMISSION": "vnf_instances:",
397 "<ID>": {"METHODS": ("GET",),
398 "ROLE_PERMISSION": "vnf_instances:id:"
405 "netslice_templates_content": {"METHODS": ("GET", "POST"),
406 "ROLE_PERMISSION": "slice_templates:",
407 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
408 "ROLE_PERMISSION": "slice_templates:id:", }
410 "netslice_templates": {"METHODS": ("GET", "POST"),
411 "ROLE_PERMISSION": "slice_templates:",
412 "<ID>": {"METHODS": ("GET", "DELETE"),
414 "ROLE_PERMISSION": "slice_templates:id:",
415 "nst_content": {"METHODS": ("GET", "PUT"),
416 "ROLE_PERMISSION": "slice_templates:id:content:"
418 "nst": {"METHODS": ("GET",), # descriptor inside package
419 "ROLE_PERMISSION": "slice_templates:id:content:"
421 "artifacts": {"*": {"METHODS": ("GET",),
422 "ROLE_PERMISSION": "slice_templates:id:content:"
427 "subscriptions": {"TODO": ("GET", "POST"),
428 "<ID>": {"TODO": ("GET", "DELETE")}
434 "netslice_instances_content": {"METHODS": ("GET", "POST"),
435 "ROLE_PERMISSION": "slice_instances:",
436 "<ID>": {"METHODS": ("GET", "DELETE"),
437 "ROLE_PERMISSION": "slice_instances:id:"
440 "netslice_instances": {"METHODS": ("GET", "POST"),
441 "ROLE_PERMISSION": "slice_instances:",
442 "<ID>": {"METHODS": ("GET", "DELETE"),
443 "ROLE_PERMISSION": "slice_instances:id:",
444 "terminate": {"METHODS": ("POST",),
445 "ROLE_PERMISSION": "slice_instances:id:terminate:"
447 "instantiate": {"METHODS": ("POST",),
448 "ROLE_PERMISSION": "slice_instances:id:instantiate:"
450 "action": {"METHODS": ("POST",),
451 "ROLE_PERMISSION": "slice_instances:id:action:"
455 "nsi_lcm_op_occs": {"METHODS": ("GET",),
456 "ROLE_PERMISSION": "slice_instances:opps:",
457 "<ID>": {"METHODS": ("GET",),
458 "ROLE_PERMISSION": "slice_instances:opps:id:",
468 "<ID>": {"METHODS": ("GET",),
469 "ROLE_PERMISSION": "reports:id:",
479 class NbiException(Exception):
481 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
482 Exception.__init
__(self
, message
)
483 self
.http_code
= http_code
486 class Server(object):
488 # to decode bytes to str
489 reader
= getreader("utf-8")
493 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
494 self
.engine
= Engine(self
.authenticator
)
496 def _format_in(self
, kwargs
):
499 if cherrypy
.request
.body
.length
:
500 error_text
= "Invalid input format "
502 if "Content-Type" in cherrypy
.request
.headers
:
503 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
504 error_text
= "Invalid json format "
505 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
506 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
507 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
508 error_text
= "Invalid yaml format "
509 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
510 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
511 elif "application/binary" in cherrypy
.request
.headers
["Content-Type"] or \
512 "application/gzip" in cherrypy
.request
.headers
["Content-Type"] or \
513 "application/zip" in cherrypy
.request
.headers
["Content-Type"] or \
514 "text/plain" in cherrypy
.request
.headers
["Content-Type"]:
515 indata
= cherrypy
.request
.body
# .read()
516 elif "multipart/form-data" in cherrypy
.request
.headers
["Content-Type"]:
517 if "descriptor_file" in kwargs
:
518 filecontent
= kwargs
.pop("descriptor_file")
519 if not filecontent
.file:
520 raise NbiException("empty file or content", HTTPStatus
.BAD_REQUEST
)
521 indata
= filecontent
.file # .read()
522 if filecontent
.content_type
.value
:
523 cherrypy
.request
.headers
["Content-Type"] = filecontent
.content_type
.value
525 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
526 # "Only 'Content-Type' of type 'application/json' or
527 # 'application/yaml' for input format are available")
528 error_text
= "Invalid yaml format "
529 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
530 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
532 error_text
= "Invalid yaml format "
533 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
534 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
539 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
542 for k
, v
in kwargs
.items():
543 if isinstance(v
, str):
548 kwargs
[k
] = yaml
.load(v
, Loader
=yaml
.SafeLoader
)
551 elif k
.endswith(".gt") or k
.endswith(".lt") or k
.endswith(".gte") or k
.endswith(".lte"):
559 elif v
.find(",") > 0:
560 kwargs
[k
] = v
.split(",")
561 elif isinstance(v
, (list, tuple)):
562 for index
in range(0, len(v
)):
567 v
[index
] = yaml
.load(v
[index
], Loader
=yaml
.SafeLoader
)
572 except (ValueError, yaml
.YAMLError
) as exc
:
573 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
574 except KeyError as exc
:
575 raise NbiException("Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
)
576 except Exception as exc
:
577 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
580 def _format_out(data
, token_info
=None, _format
=None):
582 return string of dictionary data according to requested json, yaml, xml. By default json
583 :param data: response to be sent. Can be a dict, text or file
584 :param token_info: Contains among other username and project
585 :param _format: The format to be set as Content-Type if data is a file
588 accept
= cherrypy
.request
.headers
.get("Accept")
590 if accept
and "text/html" in accept
:
591 return html
.format(data
, cherrypy
.request
, cherrypy
.response
, token_info
)
592 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
594 elif hasattr(data
, "read"): # file object
596 cherrypy
.response
.headers
["Content-Type"] = _format
597 elif "b" in data
.mode
: # binariy asssumig zip
598 cherrypy
.response
.headers
["Content-Type"] = 'application/zip'
600 cherrypy
.response
.headers
["Content-Type"] = 'text/plain'
601 # TODO check that cherrypy close file. If not implement pending things to close per thread next
604 if "application/json" in accept
:
605 cherrypy
.response
.headers
["Content-Type"] = 'application/json; charset=utf-8'
606 a
= json
.dumps(data
, indent
=4) + "\n"
607 return a
.encode("utf8")
608 elif "text/html" in accept
:
609 return html
.format(data
, cherrypy
.request
, cherrypy
.response
, token_info
)
611 elif "application/yaml" in accept
or "*/*" in accept
or "text/plain" in accept
:
613 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
614 elif cherrypy
.response
.status
>= 400:
615 raise cherrypy
.HTTPError(HTTPStatus
.NOT_ACCEPTABLE
.value
,
616 "Only 'Accept' of type 'application/json' or 'application/yaml' "
617 "for output format are available")
618 cherrypy
.response
.headers
["Content-Type"] = 'application/yaml'
619 return yaml
.safe_dump(data
, explicit_start
=True, indent
=4, default_flow_style
=False, tags
=False,
620 encoding
='utf-8', allow_unicode
=True) # , canonical=True, default_style='"'
623 def index(self
, *args
, **kwargs
):
626 if cherrypy
.request
.method
== "GET":
627 token_info
= self
.authenticator
.authorize()
628 outdata
= token_info
# Home page
630 raise cherrypy
.HTTPError(HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
631 "Method {} not allowed for tokens".format(cherrypy
.request
.method
))
633 return self
._format
_out
(outdata
, token_info
)
635 except (EngineException
, AuthException
) as e
:
636 # cherrypy.log("index Exception {}".format(e))
637 cherrypy
.response
.status
= e
.http_code
.value
638 return self
._format
_out
("Welcome to OSM!", token_info
)
641 def version(self
, *args
, **kwargs
):
642 # TODO consider to remove and provide version using the static version file
644 if cherrypy
.request
.method
!= "GET":
645 raise NbiException("Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
)
647 raise NbiException("Invalid URL or query string for version", HTTPStatus
.METHOD_NOT_ALLOWED
)
648 # TODO include version of other modules, pick up from some kafka admin message
649 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
650 return self
._format
_out
(osm_nbi_version
)
651 except NbiException
as e
:
652 cherrypy
.response
.status
= e
.http_code
.value
654 "code": e
.http_code
.name
,
655 "status": e
.http_code
.value
,
658 return self
._format
_out
(problem_details
, None)
661 def _format_login(token_info
):
663 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
665 :param token_info: Dictionary with token content
668 cherrypy
.request
.login
= token_info
.get("username", "-")
669 if token_info
.get("project_name"):
670 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
671 if token_info
.get("id"):
672 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
675 def token(self
, method
, token_id
=None, kwargs
=None):
677 # self.engine.load_dbase(cherrypy.request.app.config)
678 indata
= self
._format
_in
(kwargs
)
679 if not isinstance(indata
, dict):
680 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus
.BAD_REQUEST
)
683 token_info
= self
.authenticator
.authorize()
685 self
._format
_login
(token_info
)
687 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
689 outdata
= self
.authenticator
.get_token_list(token_info
)
690 elif method
== "POST":
692 token_info
= self
.authenticator
.authorize()
696 indata
.update(kwargs
)
697 # This is needed to log the user when authentication fails
698 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
699 outdata
= token_info
= self
.authenticator
.new_token(token_info
, indata
, cherrypy
.request
.remote
)
700 cherrypy
.session
['Authorization'] = outdata
["_id"]
701 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
703 self
._format
_login
(token_info
)
705 # cherrypy.response.cookie["Authorization"] = outdata["id"]
706 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
707 elif method
== "DELETE":
708 if not token_id
and "id" in kwargs
:
709 token_id
= kwargs
["id"]
711 token_info
= self
.authenticator
.authorize()
713 self
._format
_login
(token_info
)
714 token_id
= token_info
["_id"]
715 outdata
= self
.authenticator
.del_token(token_id
)
717 cherrypy
.session
['Authorization'] = "logout"
718 # cherrypy.response.cookie["Authorization"] = token_id
719 # cherrypy.response.cookie["Authorization"]['expires'] = 0
721 raise NbiException("Method {} not allowed for token".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
722 return self
._format
_out
(outdata
, token_info
)
725 def test(self
, *args
, **kwargs
):
726 if not cherrypy
.config
.get("server.enable_test") or (isinstance(cherrypy
.config
["server.enable_test"], str) and
727 cherrypy
.config
["server.enable_test"].lower() == "false"):
728 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
729 return "test URL is disabled"
731 if args
and args
[0] == "help":
732 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
733 "sleep/<time>\nmessage/topic\n</pre></html>"
735 elif args
and args
[0] == "init":
737 # self.engine.load_dbase(cherrypy.request.app.config)
738 self
.engine
.create_admin()
739 return "Done. User 'admin', password 'admin' created"
741 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
742 return self
._format
_out
("Database already initialized")
743 elif args
and args
[0] == "file":
744 return cherrypy
.lib
.static
.serve_file(cherrypy
.tree
.apps
['/osm'].config
["storage"]["path"] + "/" + args
[1],
745 "text/plain", "attachment")
746 elif args
and args
[0] == "file2":
747 f_path
= cherrypy
.tree
.apps
['/osm'].config
["storage"]["path"] + "/" + args
[1]
748 f
= open(f_path
, "r")
749 cherrypy
.response
.headers
["Content-type"] = "text/plain"
752 elif len(args
) == 2 and args
[0] == "db-clear":
753 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
754 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
755 elif len(args
) and args
[0] == "fs-clear":
759 folders
= self
.engine
.fs
.dir_ls(".")
760 for folder
in folders
:
761 self
.engine
.fs
.file_delete(folder
)
762 return ",".join(folders
) + " folders deleted\n"
763 elif args
and args
[0] == "login":
764 if not cherrypy
.request
.headers
.get("Authorization"):
765 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
766 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
767 elif args
and args
[0] == "login2":
768 if not cherrypy
.request
.headers
.get("Authorization"):
769 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
770 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
771 elif args
and args
[0] == "sleep":
774 sleep_time
= int(args
[1])
776 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
777 return self
._format
_out
("Database already initialized")
778 thread_info
= cherrypy
.thread_data
780 time
.sleep(sleep_time
)
782 elif len(args
) >= 2 and args
[0] == "message":
784 return_text
= "<html><pre>{} ->\n".format(main_topic
)
786 if cherrypy
.request
.method
== 'POST':
787 to_send
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
788 for k
, v
in to_send
.items():
789 self
.engine
.msg
.write(main_topic
, k
, v
)
790 return_text
+= " {}: {}\n".format(k
, v
)
791 elif cherrypy
.request
.method
== 'GET':
792 for k
, v
in kwargs
.items():
793 self
.engine
.msg
.write(main_topic
, k
, yaml
.load(v
), Loader
=yaml
.SafeLoader
)
794 return_text
+= " {}: {}\n".format(k
, yaml
.load(v
), Loader
=yaml
.SafeLoader
)
795 except Exception as e
:
796 return_text
+= "Error: " + str(e
)
797 return_text
+= "</pre></html>\n"
801 "<html><pre>\nheaders:\n args: {}\n".format(args
) +
802 " kwargs: {}\n".format(kwargs
) +
803 " headers: {}\n".format(cherrypy
.request
.headers
) +
804 " path_info: {}\n".format(cherrypy
.request
.path_info
) +
805 " query_string: {}\n".format(cherrypy
.request
.query_string
) +
806 " session: {}\n".format(cherrypy
.session
) +
807 " cookie: {}\n".format(cherrypy
.request
.cookie
) +
808 " method: {}\n".format(cherrypy
.request
.method
) +
809 " session: {}\n".format(cherrypy
.session
.get('fieldname')) +
811 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
812 if cherrypy
.request
.body
.length
:
813 return_text
+= " content: {}\n".format(
814 str(cherrypy
.request
.body
.read(int(cherrypy
.request
.headers
.get('Content-Length', 0)))))
816 return_text
+= "thread: {}\n".format(thread_info
)
817 return_text
+= "</pre></html>"
821 def _check_valid_url_method(method
, *args
):
823 raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus
.METHOD_NOT_ALLOWED
)
825 reference
= valid_url_methods
829 if not isinstance(reference
, dict):
830 raise NbiException("URL contains unexpected extra items '{}'".format(arg
),
831 HTTPStatus
.METHOD_NOT_ALLOWED
)
834 reference
= reference
[arg
]
835 elif "<ID>" in reference
:
836 reference
= reference
["<ID>"]
837 elif "*" in reference
:
838 reference
= reference
["*"]
841 raise NbiException("Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
)
842 if "TODO" in reference
and method
in reference
["TODO"]:
843 raise NbiException("Method {} not supported yet for this URL".format(method
), HTTPStatus
.NOT_IMPLEMENTED
)
844 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
845 raise NbiException("Method {} not supported for this URL".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
846 return reference
["ROLE_PERMISSION"] + method
.lower()
849 def _set_location_header(main_topic
, version
, topic
, id):
851 Insert response header Location with the URL of created item base on URL params
858 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
859 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(main_topic
, version
, topic
, id)
863 def _extract_query_string_operations(kwargs
, method
):
869 query_string_operations
= []
871 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
872 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
873 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
874 return query_string_operations
877 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
879 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
880 Check that users has rights to use them and returs the admin_query
881 :param token_info: token_info rights obtained by token
882 :param kwargs: query string input.
883 :param method: http method: GET, POSST, PUT, ...
885 :return: admin_query dictionary with keys:
886 public: True, False or None
888 project_id: tuple with projects used for accessing an element
889 set_project: tuple with projects that a created element will belong to
890 method: show, list, delete, write
892 admin_query
= {"force": False, "project_id": (token_info
["project_id"], ), "username": token_info
["username"],
893 "admin": token_info
["admin"], "public": None,
894 "allow_show_user_project_role": token_info
["allow_show_user_project_role"]}
897 if "FORCE" in kwargs
:
898 if kwargs
["FORCE"].lower() != "false": # if None or True set force to True
899 admin_query
["force"] = True
902 if "PUBLIC" in kwargs
:
903 if kwargs
["PUBLIC"].lower() != "false": # if None or True set public to True
904 admin_query
["public"] = True
906 admin_query
["public"] = False
909 if "ADMIN" in kwargs
:
910 behave_as
= kwargs
.pop("ADMIN")
911 if behave_as
.lower() != "false":
912 if not token_info
["admin"]:
913 raise NbiException("Only admin projects can use 'ADMIN' query string", HTTPStatus
.UNAUTHORIZED
)
914 if not behave_as
or behave_as
.lower() == "true": # convert True, None to empty list
915 admin_query
["project_id"] = ()
916 elif isinstance(behave_as
, (list, tuple)):
917 admin_query
["project_id"] = behave_as
918 else: # isinstance(behave_as, str)
919 admin_query
["project_id"] = (behave_as
, )
920 if "SET_PROJECT" in kwargs
:
921 set_project
= kwargs
.pop("SET_PROJECT")
923 admin_query
["set_project"] = list(admin_query
["project_id"])
925 if isinstance(set_project
, str):
926 set_project
= (set_project
, )
927 if admin_query
["project_id"]:
928 for p
in set_project
:
929 if p
not in admin_query
["project_id"]:
930 raise NbiException("Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
931 "'ADMIN='{p}'".format(p
=p
), HTTPStatus
.UNAUTHORIZED
)
932 admin_query
["set_project"] = set_project
935 # if "PROJECT_READ" in kwargs:
936 # admin_query["project"] = kwargs.pop("project")
937 # if admin_query["project"] == token_info["project_id"]:
940 admin_query
["method"] = "show"
942 admin_query
["method"] = "list"
943 elif method
== "DELETE":
944 admin_query
["method"] = "delete"
946 admin_query
["method"] = "write"
950 def default(self
, main_topic
=None, version
=None, topic
=None, _id
=None, item
=None, *args
, **kwargs
):
957 engine_session
= None
959 if not main_topic
or not version
or not topic
:
960 raise NbiException("URL must contain at least 'main_topic/version/topic'",
961 HTTPStatus
.METHOD_NOT_ALLOWED
)
962 if main_topic
not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm", "nspm"):
963 raise NbiException("URL main_topic '{}' not supported".format(main_topic
),
964 HTTPStatus
.METHOD_NOT_ALLOWED
)
966 raise NbiException("URL version '{}' not supported".format(version
), HTTPStatus
.METHOD_NOT_ALLOWED
)
968 if kwargs
and "METHOD" in kwargs
and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
969 method
= kwargs
.pop("METHOD")
971 method
= cherrypy
.request
.method
973 role_permission
= self
._check
_valid
_url
_method
(method
, main_topic
, version
, topic
, _id
, item
, *args
)
974 query_string_operations
= self
._extract
_query
_string
_operations
(kwargs
, method
)
975 if main_topic
== "admin" and topic
== "tokens":
976 return self
.token(method
, _id
, kwargs
)
977 token_info
= self
.authenticator
.authorize(role_permission
, query_string_operations
, _id
)
978 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
979 indata
= self
._format
_in
(kwargs
)
981 if topic
== "subscriptions":
982 engine_topic
= main_topic
+ "_" + topic
983 if item
and topic
!= "pm_jobs":
986 if main_topic
== "nsd":
987 engine_topic
= "nsds"
988 elif main_topic
== "vnfpkgm":
989 engine_topic
= "vnfds"
990 if topic
== "vnfpkg_op_occs":
991 engine_topic
= "vnfpkgops"
992 if topic
== "vnf_packages" and item
== "action":
993 engine_topic
= "vnfpkgops"
994 elif main_topic
== "nslcm":
995 engine_topic
= "nsrs"
996 if topic
== "ns_lcm_op_occs":
997 engine_topic
= "nslcmops"
998 if topic
== "vnfrs" or topic
== "vnf_instances":
999 engine_topic
= "vnfrs"
1000 elif main_topic
== "nst":
1001 engine_topic
= "nsts"
1002 elif main_topic
== "nsilcm":
1003 engine_topic
= "nsis"
1004 if topic
== "nsi_lcm_op_occs":
1005 engine_topic
= "nsilcmops"
1006 elif main_topic
== "pdu":
1007 engine_topic
= "pdus"
1008 if engine_topic
== "vims": # TODO this is for backward compatibility, it will be removed in the future
1009 engine_topic
= "vim_accounts"
1012 if item
in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
1013 if item
in ("vnfd", "nsd", "nst"):
1014 path
= "$DESCRIPTOR"
1017 elif item
== "artifacts":
1021 file, _format
= self
.engine
.get_file(engine_session
, engine_topic
, _id
, path
,
1022 cherrypy
.request
.headers
.get("Accept"))
1025 outdata
= self
.engine
.get_item_list(engine_session
, engine_topic
, kwargs
)
1027 if item
== "reports":
1028 # TODO check that project_id (_id in this context) has permissions
1030 outdata
= self
.engine
.get_item(engine_session
, engine_topic
, _id
)
1032 elif method
== "POST":
1033 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1034 if topic
in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"):
1035 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1037 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, {}, None,
1038 cherrypy
.request
.headers
)
1039 completed
= self
.engine
.upload_content(engine_session
, engine_topic
, _id
, indata
, kwargs
,
1040 cherrypy
.request
.headers
)
1042 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1044 cherrypy
.response
.headers
["Transaction-Id"] = _id
1045 outdata
= {"id": _id
}
1046 elif topic
== "ns_instances_content":
1048 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, indata
, kwargs
)
1050 indata
["lcmOperationType"] = "instantiate"
1051 indata
["nsInstanceId"] = _id
1052 nslcmop_id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nslcmops", indata
, None)
1053 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1054 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1055 elif topic
== "ns_instances" and item
:
1056 indata
["lcmOperationType"] = item
1057 indata
["nsInstanceId"] = _id
1058 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nslcmops", indata
, kwargs
)
1059 self
._set
_location
_header
(main_topic
, version
, "ns_lcm_op_occs", _id
)
1060 outdata
= {"id": _id
}
1061 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1062 elif topic
== "netslice_instances_content":
1063 # creates NetSlice_Instance_record (NSIR)
1064 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, indata
, kwargs
)
1065 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1066 indata
["lcmOperationType"] = "instantiate"
1067 indata
["netsliceInstanceId"] = _id
1068 nsilcmop_id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nsilcmops", indata
, kwargs
)
1069 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1070 elif topic
== "netslice_instances" and item
:
1071 indata
["lcmOperationType"] = item
1072 indata
["netsliceInstanceId"] = _id
1073 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nsilcmops", indata
, kwargs
)
1074 self
._set
_location
_header
(main_topic
, version
, "nsi_lcm_op_occs", _id
)
1075 outdata
= {"id": _id
}
1076 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1077 elif topic
== "vnf_packages" and item
== "action":
1078 indata
["lcmOperationType"] = item
1079 indata
["vnfPkgId"] = _id
1080 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, "vnfpkgops", indata
, kwargs
)
1081 self
._set
_location
_header
(main_topic
, version
, "vnfpkg_op_occs", _id
)
1082 outdata
= {"id": _id
}
1083 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1085 _id
, op_id
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, indata
, kwargs
,
1086 cherrypy
.request
.headers
)
1087 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1088 outdata
= {"id": _id
}
1090 outdata
["op_id"] = op_id
1091 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1092 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1094 elif method
== "DELETE":
1096 outdata
= self
.engine
.del_item_list(engine_session
, engine_topic
, kwargs
)
1097 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1098 else: # len(args) > 1
1099 delete_in_process
= False
1100 if topic
== "ns_instances_content" and not engine_session
["force"]:
1102 "lcmOperationType": "terminate",
1103 "nsInstanceId": _id
,
1106 opp_id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nslcmops", nslcmop_desc
, kwargs
)
1108 delete_in_process
= True
1109 outdata
= {"_id": opp_id
}
1110 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1111 elif topic
== "netslice_instances_content" and not engine_session
["force"]:
1113 "lcmOperationType": "terminate",
1114 "netsliceInstanceId": _id
,
1117 opp_id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None)
1119 delete_in_process
= True
1120 outdata
= {"_id": opp_id
}
1121 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1122 if not delete_in_process
:
1123 self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1124 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1125 if engine_topic
in ("vim_accounts", "wim_accounts", "sdns", "k8sclusters", "k8srepos"):
1126 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1128 elif method
in ("PUT", "PATCH"):
1130 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1131 raise NbiException("Nothing to update. Provide payload and/or query string",
1132 HTTPStatus
.BAD_REQUEST
)
1133 if item
in ("nsd_content", "package_content", "nst_content") and method
== "PUT":
1134 completed
= self
.engine
.upload_content(engine_session
, engine_topic
, _id
, indata
, kwargs
,
1135 cherrypy
.request
.headers
)
1137 cherrypy
.response
.headers
["Transaction-Id"] = id
1139 op_id
= self
.engine
.edit_item(engine_session
, engine_topic
, _id
, indata
, kwargs
)
1142 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1143 outdata
= {"op_id": op_id
}
1145 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1148 raise NbiException("Method {} not allowed".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
1150 # if Role information changes, it is needed to reload the information of roles
1151 if topic
== "roles" and method
!= "GET":
1152 self
.authenticator
.load_operation_to_allowed_roles()
1154 if topic
== "projects" and method
== "DELETE" \
1155 or topic
in ["users", "roles"] and method
in ["PUT", "PATCH", "DELETE"]:
1156 self
.authenticator
.remove_token_from_cache()
1158 return self
._format
_out
(outdata
, token_info
, _format
)
1159 except Exception as e
:
1160 if isinstance(e
, (NbiException
, EngineException
, DbException
, FsException
, MsgException
, AuthException
,
1161 ValidationError
, AuthconnException
)):
1162 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1163 http_code_name
= e
.http_code
.name
1164 cherrypy
.log("Exception {}".format(e
))
1166 http_code_value
= cherrypy
.response
.status
= HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1167 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1168 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1169 if hasattr(outdata
, "close"): # is an open file
1173 for rollback_item
in rollback
:
1175 if rollback_item
.get("operation") == "set":
1176 self
.engine
.db
.set_one(rollback_item
["topic"], {"_id": rollback_item
["_id"]},
1177 rollback_item
["content"], fail_on_empty
=False)
1179 self
.engine
.db
.del_one(rollback_item
["topic"], {"_id": rollback_item
["_id"]},
1180 fail_on_empty
=False)
1181 except Exception as e2
:
1182 rollback_error_text
= "Rollback Exception {}: {}".format(rollback_item
, e2
)
1183 cherrypy
.log(rollback_error_text
)
1184 error_text
+= ". " + rollback_error_text
1185 # if isinstance(e, MsgException):
1186 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1187 # engine_topic[:-1], method, error_text)
1189 "code": http_code_name
,
1190 "status": http_code_value
,
1191 "detail": error_text
,
1193 return self
._format
_out
(problem_details
, token_info
)
1194 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1197 self
._format
_login
(token_info
)
1198 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1199 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1200 if outdata
.get(logging_id
):
1201 cherrypy
.request
.login
+= ";{}={}".format(logging_id
, outdata
[logging_id
][:36])
1204 def _start_service():
1206 Callback function called when cherrypy.engine starts
1207 Override configuration with env variables
1208 Set database, storage, message configuration
1209 Init database with admin/admin user password
1212 global subscription_thread
1213 cherrypy
.log
.error("Starting osm_nbi")
1214 # update general cherrypy configuration
1217 engine_config
= cherrypy
.tree
.apps
['/osm'].config
1218 for k
, v
in environ
.items():
1219 if not k
.startswith("OSMNBI_"):
1221 k1
, _
, k2
= k
[7:].lower().partition("_")
1225 # update static configuration
1226 if k
== 'OSMNBI_STATIC_DIR':
1227 engine_config
["/static"]['tools.staticdir.dir'] = v
1228 engine_config
["/static"]['tools.staticdir.on'] = True
1229 elif k
== 'OSMNBI_SOCKET_PORT' or k
== 'OSMNBI_SERVER_PORT':
1230 update_dict
['server.socket_port'] = int(v
)
1231 elif k
== 'OSMNBI_SOCKET_HOST' or k
== 'OSMNBI_SERVER_HOST':
1232 update_dict
['server.socket_host'] = v
1233 elif k1
in ("server", "test", "auth", "log"):
1234 update_dict
[k1
+ '.' + k2
] = v
1235 elif k1
in ("message", "database", "storage", "authentication"):
1236 # k2 = k2.replace('_', '.')
1237 if k2
in ("port", "db_port"):
1238 engine_config
[k1
][k2
] = int(v
)
1240 engine_config
[k1
][k2
] = v
1242 except ValueError as e
:
1243 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1244 except Exception as e
:
1245 cherrypy
.log
.warn("skipping environ '{}' on exception '{}'".format(k
, e
))
1248 cherrypy
.config
.update(update_dict
)
1249 engine_config
["global"].update(update_dict
)
1252 log_format_simple
= "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1253 log_formatter_simple
= logging
.Formatter(log_format_simple
, datefmt
='%Y-%m-%dT%H:%M:%S')
1254 logger_server
= logging
.getLogger("cherrypy.error")
1255 logger_access
= logging
.getLogger("cherrypy.access")
1256 logger_cherry
= logging
.getLogger("cherrypy")
1257 logger_nbi
= logging
.getLogger("nbi")
1259 if "log.file" in engine_config
["global"]:
1260 file_handler
= logging
.handlers
.RotatingFileHandler(engine_config
["global"]["log.file"],
1261 maxBytes
=100e6
, backupCount
=9, delay
=0)
1262 file_handler
.setFormatter(log_formatter_simple
)
1263 logger_cherry
.addHandler(file_handler
)
1264 logger_nbi
.addHandler(file_handler
)
1265 # log always to standard output
1266 for format_
, logger
in {"nbi.server %(filename)s:%(lineno)s": logger_server
,
1267 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1268 "%(name)s %(filename)s:%(lineno)s": logger_nbi
1270 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1271 log_formatter_cherry
= logging
.Formatter(log_format_cherry
, datefmt
='%Y-%m-%dT%H:%M:%S')
1272 str_handler
= logging
.StreamHandler()
1273 str_handler
.setFormatter(log_formatter_cherry
)
1274 logger
.addHandler(str_handler
)
1276 if engine_config
["global"].get("log.level"):
1277 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1278 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1280 # logging other modules
1281 for k1
, logname
in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
1282 engine_config
[k1
]["logger_name"] = logname
1283 logger_module
= logging
.getLogger(logname
)
1284 if "logfile" in engine_config
[k1
]:
1285 file_handler
= logging
.handlers
.RotatingFileHandler(engine_config
[k1
]["logfile"],
1286 maxBytes
=100e6
, backupCount
=9, delay
=0)
1287 file_handler
.setFormatter(log_formatter_simple
)
1288 logger_module
.addHandler(file_handler
)
1289 if "loglevel" in engine_config
[k1
]:
1290 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1291 # TODO add more entries, e.g.: storage
1292 cherrypy
.tree
.apps
['/osm'].root
.engine
.start(engine_config
)
1293 cherrypy
.tree
.apps
['/osm'].root
.authenticator
.start(engine_config
)
1294 cherrypy
.tree
.apps
['/osm'].root
.engine
.init_db(target_version
=database_version
)
1295 cherrypy
.tree
.apps
['/osm'].root
.authenticator
.init_db(target_version
=auth_database_version
)
1297 # start subscriptions thread:
1298 subscription_thread
= SubscriptionThread(config
=engine_config
, engine
=nbi_server
.engine
)
1299 subscription_thread
.start()
1300 # Do not capture except SubscriptionException
1302 backend
= engine_config
["authentication"]["backend"]
1303 cherrypy
.log
.error("Starting OSM NBI Version '{} {}' with '{}' authentication backend"
1304 .format(nbi_version
, nbi_version_date
, backend
))
1307 def _stop_service():
1309 Callback function called when cherrypy.engine stops
1310 TODO: Ending database connections.
1312 global subscription_thread
1313 if subscription_thread
:
1314 subscription_thread
.terminate()
1315 subscription_thread
= None
1316 cherrypy
.tree
.apps
['/osm'].root
.engine
.stop()
1317 cherrypy
.log
.error("Stopping osm_nbi")
1320 def nbi(config_file
):
1324 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1325 # 'tools.sessions.on': True,
1326 # 'tools.response_headers.on': True,
1327 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1330 # cherrypy.Server.ssl_module = 'builtin'
1331 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1332 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1333 # cherrypy.Server.thread_pool = 10
1334 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1336 # cherrypy.config.update({'tools.auth_basic.on': True,
1337 # 'tools.auth_basic.realm': 'localhost',
1338 # 'tools.auth_basic.checkpassword': validate_password})
1339 nbi_server
= Server()
1340 cherrypy
.engine
.subscribe('start', _start_service
)
1341 cherrypy
.engine
.subscribe('stop', _stop_service
)
1342 cherrypy
.quickstart(nbi_server
, '/osm', config_file
)
1346 print("""Usage: {} [options]
1347 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1348 -h|--help: shows this help
1349 """.format(sys
.argv
[0]))
1350 # --log-socket-host HOST: send logs to this host")
1351 # --log-socket-port PORT: send logs using this port (default: 9022)")
1354 if __name__
== '__main__':
1356 # load parameters and configuration
1357 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
1358 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1361 if o
in ("-h", "--help"):
1364 elif o
in ("-c", "--config"):
1366 # elif o == "--log-socket-port":
1367 # log_socket_port = a
1368 # elif o == "--log-socket-host":
1369 # log_socket_host = a
1370 # elif o == "--log-file":
1373 assert False, "Unhandled option"
1375 if not path
.isfile(config_file
):
1376 print("configuration file '{}' that not exist".format(config_file
), file=sys
.stderr
)
1379 for config_file
in (__file__
[:__file__
.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
1380 if path
.isfile(config_file
):
1383 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys
.stderr
)
1386 except getopt
.GetoptError
as e
:
1387 print(str(e
), file=sys
.stderr
)