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
52 North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
53 URL: /osm GET POST PUT DELETE PATCH
55 /ns_descriptors_content O O
61 /artifacts[/<artifactPath>] O
69 /vnf_packages_content O O
73 /package_content O5 O5
76 /artifacts[/<artifactPath>] O5
81 /ns_instances_content O O
93 /vnf_instances (also vnfrs for compatibility) O
109 /vim_accounts (also vims for compatibility) O O
117 /netslice_templates_content O O
119 /netslice_templates O O
123 /artifacts[/<artifactPath>] O
125 /<subscriptionId> X X
128 /netslice_instances_content O O
129 /<SliceInstanceId> O O
130 /netslice_instances O O
131 /<SliceInstanceId> O O
136 /<nsiLcmOpOccId> O O O
138 /<subscriptionId> X X
141 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
142 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
143 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
144 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
146 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
147 item of the array, that is, pass if any item of the array pass the filter.
148 It allows both ne and neq for not equal
149 TODO: 4.3.3 Attribute selectors
150 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
151 (none) … same as “exclude_default”
152 all_fields … all attributes.
153 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
154 conditionally mandatory, and that are not provided in <list>.
155 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
156 are not conditionally mandatory, and that are provided in <list>.
157 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
158 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
159 the particular resource
160 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
161 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
162 present specification for the particular resource, but that are not part of <list>
163 Additionally it admits some administrator values:
164 FORCE: To force operations skipping dependency checkings
165 ADMIN: To act as an administrator or a different project
166 PUBLIC: To get public descriptors or set a descriptor as public
167 SET_PROJECT: To make a descriptor available for other project
169 Header field name Reference Example Descriptions
170 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
171 This header field shall be present if the response is expected to have a non-empty message body.
172 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
173 This header field shall be present if the request has a non-empty message body.
174 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
175 Details are specified in clause 4.5.3.
176 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
177 Header field name Reference Example Descriptions
178 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
179 This header field shall be present if the response has a non-empty message body.
180 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
181 new resource has been created.
182 This header field shall be present if the response status code is 201 or 3xx.
183 In the present document this header field is also used if the response status code is 202 and a new resource was
185 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
186 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
188 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
190 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
191 response, and the total length of the file.
192 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
195 valid_query_string
= ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
196 # ^ Contains possible administrative query string words:
197 # ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
198 # (not owned by my session project).
199 # PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
200 # FORCE=True(by default)|False: Force edition/deletion operations
201 # SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
203 valid_url_methods
= {
204 # contains allowed URL and methods, and the role_permission name
207 "tokens": {"METHODS": ("GET", "POST", "DELETE"),
208 "ROLE_PERMISSION": "tokens:",
209 "<ID>": {"METHODS": ("GET", "DELETE"),
210 "ROLE_PERMISSION": "tokens:id:"
213 "users": {"METHODS": ("GET", "POST"),
214 "ROLE_PERMISSION": "users:",
215 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
216 "ROLE_PERMISSION": "users:id:"
219 "projects": {"METHODS": ("GET", "POST"),
220 "ROLE_PERMISSION": "projects:",
221 "<ID>": {"METHODS": ("GET", "DELETE", "PUT"),
222 "ROLE_PERMISSION": "projects:id:"}
224 "roles": {"METHODS": ("GET", "POST"),
225 "ROLE_PERMISSION": "roles:",
226 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PUT"),
227 "ROLE_PERMISSION": "roles:id:"
230 "vims": {"METHODS": ("GET", "POST"),
231 "ROLE_PERMISSION": "vims:",
232 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
233 "ROLE_PERMISSION": "vims:id:"
236 "vim_accounts": {"METHODS": ("GET", "POST"),
237 "ROLE_PERMISSION": "vim_accounts:",
238 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
239 "ROLE_PERMISSION": "vim_accounts:id:"
242 "wim_accounts": {"METHODS": ("GET", "POST"),
243 "ROLE_PERMISSION": "wim_accounts:",
244 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
245 "ROLE_PERMISSION": "wim_accounts:id:"
248 "sdns": {"METHODS": ("GET", "POST"),
249 "ROLE_PERMISSION": "sdn_controllers:",
250 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
251 "ROLE_PERMISSION": "sdn_controllers:id:"
258 "pdu_descriptors": {"METHODS": ("GET", "POST"),
259 "ROLE_PERMISSION": "pduds:",
260 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
261 "ROLE_PERMISSION": "pduds:id:"
268 "ns_descriptors_content": {"METHODS": ("GET", "POST"),
269 "ROLE_PERMISSION": "nsds:",
270 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
271 "ROLE_PERMISSION": "nsds:id:"
274 "ns_descriptors": {"METHODS": ("GET", "POST"),
275 "ROLE_PERMISSION": "nsds:",
276 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
277 "ROLE_PERMISSION": "nsds:id:",
278 "nsd_content": {"METHODS": ("GET", "PUT"),
279 "ROLE_PERMISSION": "nsds:id:content:",
281 "nsd": {"METHODS": ("GET",), # descriptor inside package
282 "ROLE_PERMISSION": "nsds:id:content:"
284 "artifacts": {"*": {"METHODS": ("GET",),
285 "ROLE_PERMISSION": "nsds:id:nsd_artifact:"
290 "pnf_descriptors": {"TODO": ("GET", "POST"),
291 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
292 "pnfd_content": {"TODO": ("GET", "PUT")}
295 "subscriptions": {"TODO": ("GET", "POST"),
296 "<ID>": {"TODO": ("GET", "DELETE")}
302 "vnf_packages_content": {"METHODS": ("GET", "POST"),
303 "ROLE_PERMISSION": "vnfds:",
304 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
305 "ROLE_PERMISSION": "vnfds:id:"}
307 "vnf_packages": {"METHODS": ("GET", "POST"),
308 "ROLE_PERMISSION": "vnfds:",
309 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
310 "ROLE_PERMISSION": "vnfds:id:",
311 "package_content": {"METHODS": ("GET", "PUT"), # package
312 "ROLE_PERMISSION": "vnfds:id:",
313 "upload_from_uri": {"METHODS": (),
315 "ROLE_PERMISSION": "vnfds:id:upload:"
318 "vnfd": {"METHODS": ("GET", ), # descriptor inside package
319 "ROLE_PERMISSION": "vnfds:id:content:"
321 "artifacts": {"*": {"METHODS": ("GET", ),
322 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:"
327 "subscriptions": {"TODO": ("GET", "POST"),
328 "<ID>": {"TODO": ("GET", "DELETE")}
334 "ns_instances_content": {"METHODS": ("GET", "POST"),
335 "ROLE_PERMISSION": "ns_instances:",
336 "<ID>": {"METHODS": ("GET", "DELETE"),
337 "ROLE_PERMISSION": "ns_instances:id:"
340 "ns_instances": {"METHODS": ("GET", "POST"),
341 "ROLE_PERMISSION": "ns_instances:",
342 "<ID>": {"METHODS": ("GET", "DELETE"),
343 "ROLE_PERMISSION": "ns_instances:id:",
344 "scale": {"METHODS": ("POST",),
345 "ROLE_PERMISSION": "ns_instances:id:scale:"
347 "terminate": {"METHODS": ("POST",),
348 "ROLE_PERMISSION": "ns_instances:id:terminate:"
350 "instantiate": {"METHODS": ("POST",),
351 "ROLE_PERMISSION": "ns_instances:id:instantiate:"
353 "action": {"METHODS": ("POST",),
354 "ROLE_PERMISSION": "ns_instances:id:action:"
358 "ns_lcm_op_occs": {"METHODS": ("GET",),
359 "ROLE_PERMISSION": "ns_instances:opps:",
360 "<ID>": {"METHODS": ("GET",),
361 "ROLE_PERMISSION": "ns_instances:opps:id:"
364 "vnfrs": {"METHODS": ("GET",),
365 "ROLE_PERMISSION": "vnf_instances:",
366 "<ID>": {"METHODS": ("GET",),
367 "ROLE_PERMISSION": "vnf_instances:id:"
370 "vnf_instances": {"METHODS": ("GET",),
371 "ROLE_PERMISSION": "vnf_instances:",
372 "<ID>": {"METHODS": ("GET",),
373 "ROLE_PERMISSION": "vnf_instances:id:"
380 "netslice_templates_content": {"METHODS": ("GET", "POST"),
381 "ROLE_PERMISSION": "slice_templates:",
382 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
383 "ROLE_PERMISSION": "slice_templates:id:", }
385 "netslice_templates": {"METHODS": ("GET", "POST"),
386 "ROLE_PERMISSION": "slice_templates:",
387 "<ID>": {"METHODS": ("GET", "DELETE"),
389 "ROLE_PERMISSION": "slice_templates:id:",
390 "nst_content": {"METHODS": ("GET", "PUT"),
391 "ROLE_PERMISSION": "slice_templates:id:content:"
393 "nst": {"METHODS": ("GET",), # descriptor inside package
394 "ROLE_PERMISSION": "slice_templates:id:content:"
396 "artifacts": {"*": {"METHODS": ("GET",),
397 "ROLE_PERMISSION": "slice_templates:id:content:"
402 "subscriptions": {"TODO": ("GET", "POST"),
403 "<ID>": {"TODO": ("GET", "DELETE")}
409 "netslice_instances_content": {"METHODS": ("GET", "POST"),
410 "ROLE_PERMISSION": "slice_instances:",
411 "<ID>": {"METHODS": ("GET", "DELETE"),
412 "ROLE_PERMISSION": "slice_instances:id:"
415 "netslice_instances": {"METHODS": ("GET", "POST"),
416 "ROLE_PERMISSION": "slice_instances:",
417 "<ID>": {"METHODS": ("GET", "DELETE"),
418 "ROLE_PERMISSION": "slice_instances:id:",
419 "terminate": {"METHODS": ("POST",),
420 "ROLE_PERMISSION": "slice_instances:id:terminate:"
422 "instantiate": {"METHODS": ("POST",),
423 "ROLE_PERMISSION": "slice_instances:id:instantiate:"
425 "action": {"METHODS": ("POST",),
426 "ROLE_PERMISSION": "slice_instances:id:action:"
430 "nsi_lcm_op_occs": {"METHODS": ("GET",),
431 "ROLE_PERMISSION": "slice_instances:opps:",
432 "<ID>": {"METHODS": ("GET",),
433 "ROLE_PERMISSION": "slice_instances:opps:id:",
443 "<ID>": {"METHODS": ("GET",),
444 "ROLE_PERMISSION": "reports:id:",
454 class NbiException(Exception):
456 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
457 Exception.__init
__(self
, message
)
458 self
.http_code
= http_code
461 class Server(object):
463 # to decode bytes to str
464 reader
= getreader("utf-8")
468 self
.engine
= Engine()
469 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
471 def _format_in(self
, kwargs
):
474 if cherrypy
.request
.body
.length
:
475 error_text
= "Invalid input format "
477 if "Content-Type" in cherrypy
.request
.headers
:
478 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
479 error_text
= "Invalid json format "
480 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
481 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
482 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
483 error_text
= "Invalid yaml format "
484 indata
= yaml
.load(cherrypy
.request
.body
)
485 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
486 elif "application/binary" in cherrypy
.request
.headers
["Content-Type"] or \
487 "application/gzip" in cherrypy
.request
.headers
["Content-Type"] or \
488 "application/zip" in cherrypy
.request
.headers
["Content-Type"] or \
489 "text/plain" in cherrypy
.request
.headers
["Content-Type"]:
490 indata
= cherrypy
.request
.body
# .read()
491 elif "multipart/form-data" in cherrypy
.request
.headers
["Content-Type"]:
492 if "descriptor_file" in kwargs
:
493 filecontent
= kwargs
.pop("descriptor_file")
494 if not filecontent
.file:
495 raise NbiException("empty file or content", HTTPStatus
.BAD_REQUEST
)
496 indata
= filecontent
.file # .read()
497 if filecontent
.content_type
.value
:
498 cherrypy
.request
.headers
["Content-Type"] = filecontent
.content_type
.value
500 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
501 # "Only 'Content-Type' of type 'application/json' or
502 # 'application/yaml' for input format are available")
503 error_text
= "Invalid yaml format "
504 indata
= yaml
.load(cherrypy
.request
.body
)
505 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
507 error_text
= "Invalid yaml format "
508 indata
= yaml
.load(cherrypy
.request
.body
)
509 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
514 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
517 for k
, v
in kwargs
.items():
518 if isinstance(v
, str):
523 kwargs
[k
] = yaml
.load(v
)
526 elif k
.endswith(".gt") or k
.endswith(".lt") or k
.endswith(".gte") or k
.endswith(".lte"):
534 elif v
.find(",") > 0:
535 kwargs
[k
] = v
.split(",")
536 elif isinstance(v
, (list, tuple)):
537 for index
in range(0, len(v
)):
542 v
[index
] = yaml
.load(v
[index
])
547 except (ValueError, yaml
.YAMLError
) as exc
:
548 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
549 except KeyError as exc
:
550 raise NbiException("Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
)
551 except Exception as exc
:
552 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
555 def _format_out(data
, token_info
=None, _format
=None):
557 return string of dictionary data according to requested json, yaml, xml. By default json
558 :param data: response to be sent. Can be a dict, text or file
559 :param token_info: Contains among other username and project
560 :param _format: The format to be set as Content-Type ir data is a file
563 accept
= cherrypy
.request
.headers
.get("Accept")
565 if accept
and "text/html" in accept
:
566 return html
.format(data
, cherrypy
.request
, cherrypy
.response
, token_info
)
567 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
569 elif hasattr(data
, "read"): # file object
571 cherrypy
.response
.headers
["Content-Type"] = _format
572 elif "b" in data
.mode
: # binariy asssumig zip
573 cherrypy
.response
.headers
["Content-Type"] = 'application/zip'
575 cherrypy
.response
.headers
["Content-Type"] = 'text/plain'
576 # TODO check that cherrypy close file. If not implement pending things to close per thread next
579 if "application/json" in accept
:
580 cherrypy
.response
.headers
["Content-Type"] = 'application/json; charset=utf-8'
581 a
= json
.dumps(data
, indent
=4) + "\n"
582 return a
.encode("utf8")
583 elif "text/html" in accept
:
584 return html
.format(data
, cherrypy
.request
, cherrypy
.response
, token_info
)
586 elif "application/yaml" in accept
or "*/*" in accept
or "text/plain" in accept
:
588 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
589 elif cherrypy
.response
.status
>= 400:
590 raise cherrypy
.HTTPError(HTTPStatus
.NOT_ACCEPTABLE
.value
,
591 "Only 'Accept' of type 'application/json' or 'application/yaml' "
592 "for output format are available")
593 cherrypy
.response
.headers
["Content-Type"] = 'application/yaml'
594 return yaml
.safe_dump(data
, explicit_start
=True, indent
=4, default_flow_style
=False, tags
=False,
595 encoding
='utf-8', allow_unicode
=True) # , canonical=True, default_style='"'
598 def index(self
, *args
, **kwargs
):
601 if cherrypy
.request
.method
== "GET":
602 token_info
= self
.authenticator
.authorize()
603 outdata
= token_info
# Home page
605 raise cherrypy
.HTTPError(HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
606 "Method {} not allowed for tokens".format(cherrypy
.request
.method
))
608 return self
._format
_out
(outdata
, token_info
)
610 except (EngineException
, AuthException
) as e
:
611 # cherrypy.log("index Exception {}".format(e))
612 cherrypy
.response
.status
= e
.http_code
.value
613 return self
._format
_out
("Welcome to OSM!", token_info
)
616 def version(self
, *args
, **kwargs
):
617 # TODO consider to remove and provide version using the static version file
619 if cherrypy
.request
.method
!= "GET":
620 raise NbiException("Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
)
622 raise NbiException("Invalid URL or query string for version", HTTPStatus
.METHOD_NOT_ALLOWED
)
623 # TODO include version of other modules, pick up from some kafka admin message
624 return "<pre>NBI:\n version: {}\n date: {}\n".format(nbi_version
, nbi_version_date
)
625 except NbiException
as e
:
626 cherrypy
.response
.status
= e
.http_code
.value
628 "code": e
.http_code
.name
,
629 "status": e
.http_code
.value
,
632 return self
._format
_out
(problem_details
, None)
635 def _format_login(token_info
):
637 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
639 :param token_info: Dictionary with token content
642 cherrypy
.request
.login
= token_info
.get("username", "-")
643 if token_info
.get("project_name"):
644 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
645 if token_info
.get("id"):
646 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
649 def token(self
, method
, token_id
=None, kwargs
=None):
651 # self.engine.load_dbase(cherrypy.request.app.config)
652 indata
= self
._format
_in
(kwargs
)
653 if not isinstance(indata
, dict):
654 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus
.BAD_REQUEST
)
657 token_info
= self
.authenticator
.authorize()
659 self
._format
_login
(token_info
)
661 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
663 outdata
= self
.authenticator
.get_token_list(token_info
)
664 elif method
== "POST":
666 token_info
= self
.authenticator
.authorize()
670 indata
.update(kwargs
)
671 # This is needed to log the user when authentication fails
672 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
673 outdata
= token_info
= self
.authenticator
.new_token(token_info
, indata
, cherrypy
.request
.remote
)
674 cherrypy
.session
['Authorization'] = outdata
["_id"]
675 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
677 self
._format
_login
(token_info
)
679 # cherrypy.response.cookie["Authorization"] = outdata["id"]
680 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
681 elif method
== "DELETE":
682 if not token_id
and "id" in kwargs
:
683 token_id
= kwargs
["id"]
685 token_info
= self
.authenticator
.authorize()
687 self
._format
_login
(token_info
)
688 token_id
= token_info
["_id"]
689 outdata
= self
.authenticator
.del_token(token_id
)
691 cherrypy
.session
['Authorization'] = "logout"
692 # cherrypy.response.cookie["Authorization"] = token_id
693 # cherrypy.response.cookie["Authorization"]['expires'] = 0
695 raise NbiException("Method {} not allowed for token".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
696 return self
._format
_out
(outdata
, token_info
)
699 def test(self
, *args
, **kwargs
):
701 if args
and args
[0] == "help":
702 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
703 "sleep/<time>\nmessage/topic\n</pre></html>"
705 elif args
and args
[0] == "init":
707 # self.engine.load_dbase(cherrypy.request.app.config)
708 self
.engine
.create_admin()
709 return "Done. User 'admin', password 'admin' created"
711 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
712 return self
._format
_out
("Database already initialized")
713 elif args
and args
[0] == "file":
714 return cherrypy
.lib
.static
.serve_file(cherrypy
.tree
.apps
['/osm'].config
["storage"]["path"] + "/" + args
[1],
715 "text/plain", "attachment")
716 elif args
and args
[0] == "file2":
717 f_path
= cherrypy
.tree
.apps
['/osm'].config
["storage"]["path"] + "/" + args
[1]
718 f
= open(f_path
, "r")
719 cherrypy
.response
.headers
["Content-type"] = "text/plain"
722 elif len(args
) == 2 and args
[0] == "db-clear":
723 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
724 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
725 elif len(args
) and args
[0] == "fs-clear":
729 folders
= self
.engine
.fs
.dir_ls(".")
730 for folder
in folders
:
731 self
.engine
.fs
.file_delete(folder
)
732 return ",".join(folders
) + " folders deleted\n"
733 elif args
and args
[0] == "login":
734 if not cherrypy
.request
.headers
.get("Authorization"):
735 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
736 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
737 elif args
and args
[0] == "login2":
738 if not cherrypy
.request
.headers
.get("Authorization"):
739 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
740 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
741 elif args
and args
[0] == "sleep":
744 sleep_time
= int(args
[1])
746 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
747 return self
._format
_out
("Database already initialized")
748 thread_info
= cherrypy
.thread_data
750 time
.sleep(sleep_time
)
752 elif len(args
) >= 2 and args
[0] == "message":
754 return_text
= "<html><pre>{} ->\n".format(main_topic
)
756 if cherrypy
.request
.method
== 'POST':
757 to_send
= yaml
.load(cherrypy
.request
.body
)
758 for k
, v
in to_send
.items():
759 self
.engine
.msg
.write(main_topic
, k
, v
)
760 return_text
+= " {}: {}\n".format(k
, v
)
761 elif cherrypy
.request
.method
== 'GET':
762 for k
, v
in kwargs
.items():
763 self
.engine
.msg
.write(main_topic
, k
, yaml
.load(v
))
764 return_text
+= " {}: {}\n".format(k
, yaml
.load(v
))
765 except Exception as e
:
766 return_text
+= "Error: " + str(e
)
767 return_text
+= "</pre></html>\n"
771 "<html><pre>\nheaders:\n args: {}\n".format(args
) +
772 " kwargs: {}\n".format(kwargs
) +
773 " headers: {}\n".format(cherrypy
.request
.headers
) +
774 " path_info: {}\n".format(cherrypy
.request
.path_info
) +
775 " query_string: {}\n".format(cherrypy
.request
.query_string
) +
776 " session: {}\n".format(cherrypy
.session
) +
777 " cookie: {}\n".format(cherrypy
.request
.cookie
) +
778 " method: {}\n".format(cherrypy
.request
.method
) +
779 " session: {}\n".format(cherrypy
.session
.get('fieldname')) +
781 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
782 if cherrypy
.request
.body
.length
:
783 return_text
+= " content: {}\n".format(
784 str(cherrypy
.request
.body
.read(int(cherrypy
.request
.headers
.get('Content-Length', 0)))))
786 return_text
+= "thread: {}\n".format(thread_info
)
787 return_text
+= "</pre></html>"
791 def _check_valid_url_method(method
, *args
):
793 raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus
.METHOD_NOT_ALLOWED
)
795 reference
= valid_url_methods
799 if not isinstance(reference
, dict):
800 raise NbiException("URL contains unexpected extra items '{}'".format(arg
),
801 HTTPStatus
.METHOD_NOT_ALLOWED
)
804 reference
= reference
[arg
]
805 elif "<ID>" in reference
:
806 reference
= reference
["<ID>"]
807 elif "*" in reference
:
808 reference
= reference
["*"]
811 raise NbiException("Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
)
812 if "TODO" in reference
and method
in reference
["TODO"]:
813 raise NbiException("Method {} not supported yet for this URL".format(method
), HTTPStatus
.NOT_IMPLEMENTED
)
814 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
815 raise NbiException("Method {} not supported for this URL".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
816 return reference
["ROLE_PERMISSION"] + method
.lower()
819 def _set_location_header(main_topic
, version
, topic
, id):
821 Insert response header Location with the URL of created item base on URL params
828 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
829 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(main_topic
, version
, topic
, id)
833 def _extract_query_string_operations(kwargs
, method
):
839 query_string_operations
= []
841 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
842 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
843 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
844 return query_string_operations
847 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
849 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
850 Check that users has rights to use them and returs the admin_query
851 :param token_info: token_info rights obtained by token
852 :param kwargs: query string input.
853 :param method: http method: GET, POSST, PUT, ...
855 :return: admin_query dictionary with keys:
856 public: True, False or None
858 project_id: tuple with projects used for accessing an element
859 set_project: tuple with projects that a created element will belong to
860 method: show, list, delete, write
862 admin_query
= {"force": False, "project_id": (token_info
["project_id"], ), "username": token_info
["username"],
863 "admin": token_info
["admin"], "public": None}
866 if "FORCE" in kwargs
:
867 if kwargs
["FORCE"].lower() != "false": # if None or True set force to True
868 admin_query
["force"] = True
871 if "PUBLIC" in kwargs
:
872 if kwargs
["PUBLIC"].lower() != "false": # if None or True set public to True
873 admin_query
["public"] = True
875 admin_query
["public"] = False
878 if "ADMIN" in kwargs
:
879 behave_as
= kwargs
.pop("ADMIN")
880 if behave_as
.lower() != "false":
881 if not token_info
["admin"]:
882 raise NbiException("Only admin projects can use 'ADMIN' query string", HTTPStatus
.UNAUTHORIZED
)
883 if not behave_as
or behave_as
.lower() == "true": # convert True, None to empty list
884 admin_query
["project_id"] = ()
885 elif isinstance(behave_as
, (list, tuple)):
886 admin_query
["project_id"] = behave_as
887 else: # isinstance(behave_as, str)
888 admin_query
["project_id"] = (behave_as
, )
889 if "SET_PROJECT" in kwargs
:
890 set_project
= kwargs
.pop("SET_PROJECT")
892 admin_query
["set_project"] = list(admin_query
["project_id"])
894 if isinstance(set_project
, str):
895 set_project
= (set_project
, )
896 if admin_query
["project_id"]:
897 for p
in set_project
:
898 if p
not in admin_query
["project_id"]:
899 raise NbiException("Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
900 "'ADMIN='{p}'".format(p
=p
), HTTPStatus
.UNAUTHORIZED
)
901 admin_query
["set_project"] = set_project
904 # if "PROJECT_READ" in kwargs:
905 # admin_query["project"] = kwargs.pop("project")
906 # if admin_query["project"] == token_info["project_id"]:
909 admin_query
["method"] = "show"
911 admin_query
["method"] = "list"
912 elif method
== "DELETE":
913 admin_query
["method"] = "delete"
915 admin_query
["method"] = "write"
919 def default(self
, main_topic
=None, version
=None, topic
=None, _id
=None, item
=None, *args
, **kwargs
):
926 engine_session
= None
928 if not main_topic
or not version
or not topic
:
929 raise NbiException("URL must contain at least 'main_topic/version/topic'",
930 HTTPStatus
.METHOD_NOT_ALLOWED
)
931 if main_topic
not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm", "nspm"):
932 raise NbiException("URL main_topic '{}' not supported".format(main_topic
),
933 HTTPStatus
.METHOD_NOT_ALLOWED
)
935 raise NbiException("URL version '{}' not supported".format(version
), HTTPStatus
.METHOD_NOT_ALLOWED
)
937 if kwargs
and "METHOD" in kwargs
and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
938 method
= kwargs
.pop("METHOD")
940 method
= cherrypy
.request
.method
942 role_permission
= self
._check
_valid
_url
_method
(method
, main_topic
, version
, topic
, _id
, item
, *args
)
943 query_string_operations
= self
._extract
_query
_string
_operations
(kwargs
, method
)
944 if main_topic
== "admin" and topic
== "tokens":
945 return self
.token(method
, _id
, kwargs
)
947 token_info
= self
.authenticator
.authorize(role_permission
, query_string_operations
)
948 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
949 indata
= self
._format
_in
(kwargs
)
951 if topic
== "subscriptions":
952 engine_topic
= main_topic
+ "_" + topic
953 if item
and topic
!= "pm_jobs":
956 if main_topic
== "nsd":
957 engine_topic
= "nsds"
958 elif main_topic
== "vnfpkgm":
959 engine_topic
= "vnfds"
960 elif main_topic
== "nslcm":
961 engine_topic
= "nsrs"
962 if topic
== "ns_lcm_op_occs":
963 engine_topic
= "nslcmops"
964 if topic
== "vnfrs" or topic
== "vnf_instances":
965 engine_topic
= "vnfrs"
966 elif main_topic
== "nst":
967 engine_topic
= "nsts"
968 elif main_topic
== "nsilcm":
969 engine_topic
= "nsis"
970 if topic
== "nsi_lcm_op_occs":
971 engine_topic
= "nsilcmops"
972 elif main_topic
== "pdu":
973 engine_topic
= "pdus"
974 if engine_topic
== "vims": # TODO this is for backward compatibility, it will be removed in the future
975 engine_topic
= "vim_accounts"
978 if item
in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
979 if item
in ("vnfd", "nsd", "nst"):
983 elif item
== "artifacts":
987 file, _format
= self
.engine
.get_file(engine_session
, engine_topic
, _id
, path
,
988 cherrypy
.request
.headers
.get("Accept"))
991 outdata
= self
.engine
.get_item_list(engine_session
, engine_topic
, kwargs
)
993 if item
== "reports":
994 # TODO check that project_id (_id in this context) has permissions
996 outdata
= self
.engine
.get_item(engine_session
, engine_topic
, _id
)
997 elif method
== "POST":
998 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
999 if topic
in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"):
1000 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1002 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, {}, None,
1003 cherrypy
.request
.headers
)
1004 completed
= self
.engine
.upload_content(engine_session
, engine_topic
, _id
, indata
, kwargs
,
1005 cherrypy
.request
.headers
)
1007 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1009 cherrypy
.response
.headers
["Transaction-Id"] = _id
1010 outdata
= {"id": _id
}
1011 elif topic
== "ns_instances_content":
1013 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, indata
, kwargs
)
1015 indata
["lcmOperationType"] = "instantiate"
1016 indata
["nsInstanceId"] = _id
1017 nslcmop_id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nslcmops", indata
, None)
1018 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1019 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1020 elif topic
== "ns_instances" and item
:
1021 indata
["lcmOperationType"] = item
1022 indata
["nsInstanceId"] = _id
1023 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nslcmops", indata
, kwargs
)
1024 self
._set
_location
_header
(main_topic
, version
, "ns_lcm_op_occs", _id
)
1025 outdata
= {"id": _id
}
1026 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1027 elif topic
== "netslice_instances_content":
1028 # creates NetSlice_Instance_record (NSIR)
1029 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, indata
, kwargs
)
1030 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1031 indata
["lcmOperationType"] = "instantiate"
1032 indata
["netsliceInstanceId"] = _id
1033 nsilcmop_id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nsilcmops", indata
, kwargs
)
1034 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1036 elif topic
== "netslice_instances" and item
:
1037 indata
["lcmOperationType"] = item
1038 indata
["netsliceInstanceId"] = _id
1039 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nsilcmops", indata
, kwargs
)
1040 self
._set
_location
_header
(main_topic
, version
, "nsi_lcm_op_occs", _id
)
1041 outdata
= {"id": _id
}
1042 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1044 _id
, op_id
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, indata
, kwargs
,
1045 cherrypy
.request
.headers
)
1046 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1047 outdata
= {"id": _id
}
1049 outdata
["op_id"] = op_id
1050 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1051 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1053 elif method
== "DELETE":
1055 outdata
= self
.engine
.del_item_list(engine_session
, engine_topic
, kwargs
)
1056 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1057 else: # len(args) > 1
1058 delete_in_process
= False
1059 if topic
== "ns_instances_content" and not engine_session
["force"]:
1061 "lcmOperationType": "terminate",
1062 "nsInstanceId": _id
,
1065 opp_id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nslcmops", nslcmop_desc
, None)
1067 delete_in_process
= True
1068 outdata
= {"_id": opp_id
}
1069 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1070 elif topic
== "netslice_instances_content" and not engine_session
["force"]:
1072 "lcmOperationType": "terminate",
1073 "netsliceInstanceId": _id
,
1076 opp_id
, _
= self
.engine
.new_item(rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None)
1078 delete_in_process
= True
1079 outdata
= {"_id": opp_id
}
1080 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1081 if not delete_in_process
:
1082 self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1083 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1084 if engine_topic
in ("vim_accounts", "wim_accounts", "sdns"):
1085 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1087 elif method
in ("PUT", "PATCH"):
1089 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1090 raise NbiException("Nothing to update. Provide payload and/or query string",
1091 HTTPStatus
.BAD_REQUEST
)
1092 if item
in ("nsd_content", "package_content", "nst_content") and method
== "PUT":
1093 completed
= self
.engine
.upload_content(engine_session
, engine_topic
, _id
, indata
, kwargs
,
1094 cherrypy
.request
.headers
)
1096 cherrypy
.response
.headers
["Transaction-Id"] = id
1098 op_id
= self
.engine
.edit_item(engine_session
, engine_topic
, _id
, indata
, kwargs
)
1101 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1102 outdata
= {"op_id": op_id
}
1104 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1107 raise NbiException("Method {} not allowed".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
1109 # if Role information changes, it is needed to reload the information of roles
1110 if topic
== "roles" and method
!= "GET":
1111 self
.authenticator
.load_operation_to_allowed_roles()
1112 return self
._format
_out
(outdata
, token_info
, _format
)
1113 except Exception as e
:
1114 if isinstance(e
, (NbiException
, EngineException
, DbException
, FsException
, MsgException
, AuthException
,
1115 ValidationError
, AuthconnException
)):
1116 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1117 http_code_name
= e
.http_code
.name
1118 cherrypy
.log("Exception {}".format(e
))
1120 http_code_value
= cherrypy
.response
.status
= HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1121 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1122 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1123 if hasattr(outdata
, "close"): # is an open file
1127 for rollback_item
in rollback
:
1129 if rollback_item
.get("operation") == "set":
1130 self
.engine
.db
.set_one(rollback_item
["topic"], {"_id": rollback_item
["_id"]},
1131 rollback_item
["content"], fail_on_empty
=False)
1133 self
.engine
.db
.del_one(rollback_item
["topic"], {"_id": rollback_item
["_id"]},
1134 fail_on_empty
=False)
1135 except Exception as e2
:
1136 rollback_error_text
= "Rollback Exception {}: {}".format(rollback_item
, e2
)
1137 cherrypy
.log(rollback_error_text
)
1138 error_text
+= ". " + rollback_error_text
1139 # if isinstance(e, MsgException):
1140 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1141 # engine_topic[:-1], method, error_text)
1143 "code": http_code_name
,
1144 "status": http_code_value
,
1145 "detail": error_text
,
1147 return self
._format
_out
(problem_details
, token_info
)
1148 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1151 self
._format
_login
(token_info
)
1152 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1153 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1154 if outdata
.get(logging_id
):
1155 cherrypy
.request
.login
+= ";{}={}".format(logging_id
, outdata
[logging_id
][:36])
1158 def _start_service():
1160 Callback function called when cherrypy.engine starts
1161 Override configuration with env variables
1162 Set database, storage, message configuration
1163 Init database with admin/admin user password
1166 global subscription_thread
1167 cherrypy
.log
.error("Starting osm_nbi")
1168 # update general cherrypy configuration
1171 engine_config
= cherrypy
.tree
.apps
['/osm'].config
1172 for k
, v
in environ
.items():
1173 if not k
.startswith("OSMNBI_"):
1175 k1
, _
, k2
= k
[7:].lower().partition("_")
1179 # update static configuration
1180 if k
== 'OSMNBI_STATIC_DIR':
1181 engine_config
["/static"]['tools.staticdir.dir'] = v
1182 engine_config
["/static"]['tools.staticdir.on'] = True
1183 elif k
== 'OSMNBI_SOCKET_PORT' or k
== 'OSMNBI_SERVER_PORT':
1184 update_dict
['server.socket_port'] = int(v
)
1185 elif k
== 'OSMNBI_SOCKET_HOST' or k
== 'OSMNBI_SERVER_HOST':
1186 update_dict
['server.socket_host'] = v
1187 elif k1
in ("server", "test", "auth", "log"):
1188 update_dict
[k1
+ '.' + k2
] = v
1189 elif k1
in ("message", "database", "storage", "authentication"):
1190 # k2 = k2.replace('_', '.')
1191 if k2
in ("port", "db_port"):
1192 engine_config
[k1
][k2
] = int(v
)
1194 engine_config
[k1
][k2
] = v
1196 except ValueError as e
:
1197 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1198 except Exception as e
:
1199 cherrypy
.log
.warn("skipping environ '{}' on exception '{}'".format(k
, e
))
1202 cherrypy
.config
.update(update_dict
)
1203 engine_config
["global"].update(update_dict
)
1206 log_format_simple
= "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1207 log_formatter_simple
= logging
.Formatter(log_format_simple
, datefmt
='%Y-%m-%dT%H:%M:%S')
1208 logger_server
= logging
.getLogger("cherrypy.error")
1209 logger_access
= logging
.getLogger("cherrypy.access")
1210 logger_cherry
= logging
.getLogger("cherrypy")
1211 logger_nbi
= logging
.getLogger("nbi")
1213 if "log.file" in engine_config
["global"]:
1214 file_handler
= logging
.handlers
.RotatingFileHandler(engine_config
["global"]["log.file"],
1215 maxBytes
=100e6
, backupCount
=9, delay
=0)
1216 file_handler
.setFormatter(log_formatter_simple
)
1217 logger_cherry
.addHandler(file_handler
)
1218 logger_nbi
.addHandler(file_handler
)
1219 # log always to standard output
1220 for format_
, logger
in {"nbi.server %(filename)s:%(lineno)s": logger_server
,
1221 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1222 "%(name)s %(filename)s:%(lineno)s": logger_nbi
1224 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1225 log_formatter_cherry
= logging
.Formatter(log_format_cherry
, datefmt
='%Y-%m-%dT%H:%M:%S')
1226 str_handler
= logging
.StreamHandler()
1227 str_handler
.setFormatter(log_formatter_cherry
)
1228 logger
.addHandler(str_handler
)
1230 if engine_config
["global"].get("log.level"):
1231 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1232 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1234 # logging other modules
1235 for k1
, logname
in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
1236 engine_config
[k1
]["logger_name"] = logname
1237 logger_module
= logging
.getLogger(logname
)
1238 if "logfile" in engine_config
[k1
]:
1239 file_handler
= logging
.handlers
.RotatingFileHandler(engine_config
[k1
]["logfile"],
1240 maxBytes
=100e6
, backupCount
=9, delay
=0)
1241 file_handler
.setFormatter(log_formatter_simple
)
1242 logger_module
.addHandler(file_handler
)
1243 if "loglevel" in engine_config
[k1
]:
1244 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1245 # TODO add more entries, e.g.: storage
1246 cherrypy
.tree
.apps
['/osm'].root
.engine
.start(engine_config
)
1247 cherrypy
.tree
.apps
['/osm'].root
.authenticator
.start(engine_config
)
1248 cherrypy
.tree
.apps
['/osm'].root
.engine
.init_db(target_version
=database_version
)
1249 cherrypy
.tree
.apps
['/osm'].root
.authenticator
.init_db(target_version
=auth_database_version
)
1251 # start subscriptions thread:
1252 subscription_thread
= SubscriptionThread(config
=engine_config
, engine
=nbi_server
.engine
)
1253 subscription_thread
.start()
1254 # Do not capture except SubscriptionException
1256 # load and print version. Ignore possible errors, e.g. file not found
1258 backend
= engine_config
["authentication"]["backend"]
1260 cherrypy
.log
.error("Starting OSM NBI Version '{}' with '{}' authentication backend"
1261 .format(nbi_version
+ " " + nbi_version_date
, backend
))
1266 def _stop_service():
1268 Callback function called when cherrypy.engine stops
1269 TODO: Ending database connections.
1271 global subscription_thread
1272 if subscription_thread
:
1273 subscription_thread
.terminate()
1274 subscription_thread
= None
1275 cherrypy
.tree
.apps
['/osm'].root
.engine
.stop()
1276 cherrypy
.log
.error("Stopping osm_nbi")
1279 def nbi(config_file
):
1283 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1284 # 'tools.sessions.on': True,
1285 # 'tools.response_headers.on': True,
1286 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1289 # cherrypy.Server.ssl_module = 'builtin'
1290 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1291 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1292 # cherrypy.Server.thread_pool = 10
1293 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1295 # cherrypy.config.update({'tools.auth_basic.on': True,
1296 # 'tools.auth_basic.realm': 'localhost',
1297 # 'tools.auth_basic.checkpassword': validate_password})
1298 nbi_server
= Server()
1299 cherrypy
.engine
.subscribe('start', _start_service
)
1300 cherrypy
.engine
.subscribe('stop', _stop_service
)
1301 cherrypy
.quickstart(nbi_server
, '/osm', config_file
)
1305 print("""Usage: {} [options]
1306 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1307 -h|--help: shows this help
1308 """.format(sys
.argv
[0]))
1309 # --log-socket-host HOST: send logs to this host")
1310 # --log-socket-port PORT: send logs using this port (default: 9022)")
1313 if __name__
== '__main__':
1315 # load parameters and configuration
1316 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
1317 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1320 if o
in ("-h", "--help"):
1323 elif o
in ("-c", "--config"):
1325 # elif o == "--log-socket-port":
1326 # log_socket_port = a
1327 # elif o == "--log-socket-host":
1328 # log_socket_host = a
1329 # elif o == "--log-file":
1332 assert False, "Unhandled option"
1334 if not path
.isfile(config_file
):
1335 print("configuration file '{}' that not exist".format(config_file
), file=sys
.stderr
)
1338 for config_file
in (__file__
[:__file__
.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
1339 if path
.isfile(config_file
):
1342 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys
.stderr
)
1345 except getopt
.GetoptError
as e
:
1346 print(str(e
), file=sys
.stderr
)