9188834ff0d674c3a3fdd3124c1f951869b01f57
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 html_out
as html
23 import logging
.handlers
27 from authconn
import AuthException
28 from auth
import Authenticator
29 from engine
import Engine
, EngineException
30 from subscriptions
import SubscriptionThread
31 from 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
39 __author__
= "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
42 version_date
= "Jan 2019"
43 database_version
= '1.2'
44 auth_database_version
= '1.0'
45 nbi_server
= None # instance of Server class
46 subscription_thread
= None # instance of SubscriptionThread class
50 North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
51 URL: /osm GET POST PUT DELETE PATCH
53 /ns_descriptors_content O O
59 /artifacts[/<artifactPath>] O
67 /vnf_packages_content O O
71 /package_content O5 O5
74 /artifacts[/<artifactPath>] O5
79 /ns_instances_content O O
91 /vnf_instances (also vnfrs for compatibility) O
107 /vim_accounts (also vims for compatibility) O O
115 /netslice_templates_content O O
117 /netslice_templates O O
121 /artifacts[/<artifactPath>] O
123 /<subscriptionId> X X
126 /netslice_instances_content O O
127 /<SliceInstanceId> O O
128 /netslice_instances O O
129 /<SliceInstanceId> O O
134 /<nsiLcmOpOccId> O O O
136 /<subscriptionId> X X
139 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
140 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
141 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
142 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
144 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
145 item of the array, that is, pass if any item of the array pass the filter.
146 It allows both ne and neq for not equal
147 TODO: 4.3.3 Attribute selectors
148 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
149 (none) … same as “exclude_default”
150 all_fields … all attributes.
151 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
152 conditionally mandatory, and that are not provided in <list>.
153 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
154 are not conditionally mandatory, and that are provided in <list>.
155 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
156 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
157 the particular resource
158 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
159 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
160 present specification for the particular resource, but that are not part of <list>
161 Additionally it admits some administrator values:
162 FORCE: To force operations skipping dependency checkings
163 ADMIN: To act as an administrator or a different project
164 PUBLIC: To get public descriptors or set a descriptor as public
165 SET_PROJECT: To make a descriptor available for other project
167 Header field name Reference Example Descriptions
168 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
169 This header field shall be present if the response is expected to have a non-empty message body.
170 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
171 This header field shall be present if the request has a non-empty message body.
172 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
173 Details are specified in clause 4.5.3.
174 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
175 Header field name Reference Example Descriptions
176 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
177 This header field shall be present if the response has a non-empty message body.
178 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
179 new resource has been created.
180 This header field shall be present if the response status code is 201 or 3xx.
181 In the present document this header field is also used if the response status code is 202 and a new resource was
183 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
184 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
186 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
188 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
189 response, and the total length of the file.
190 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
193 valid_query_string
= ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
194 # ^ Contains possible administrative query string words:
195 # ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
196 # (not owned by my session project).
197 # PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
198 # FORCE=True(by default)|False: Force edition/deletion operations
199 # SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
201 valid_url_methods
= {
202 # contains allowed URL and methods, and the role_permission name
205 "tokens": {"METHODS": ("GET", "POST", "DELETE"),
206 "ROLE_PERMISSION": "tokens:",
207 "<ID>": {"METHODS": ("GET", "DELETE"),
208 "ROLE_PERMISSION": "tokens:id:"
211 "users": {"METHODS": ("GET", "POST"),
212 "ROLE_PERMISSION": "users:",
213 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
214 "ROLE_PERMISSION": "users:id:"
217 "projects": {"METHODS": ("GET", "POST"),
218 "ROLE_PERMISSION": "projects:",
219 "<ID>": {"METHODS": ("GET", "DELETE", "PUT"),
220 "ROLE_PERMISSION": "projects:id:"}
222 "roles": {"METHODS": ("GET", "POST"),
223 "ROLE_PERMISSION": "roles:",
224 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PUT"),
225 "ROLE_PERMISSION": "roles:id:"
228 "vims": {"METHODS": ("GET", "POST"),
229 "ROLE_PERMISSION": "vims:",
230 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
231 "ROLE_PERMISSION": "vims:id:"
234 "vim_accounts": {"METHODS": ("GET", "POST"),
235 "ROLE_PERMISSION": "vim_accounts:",
236 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
237 "ROLE_PERMISSION": "vim_accounts:id:"
240 "wim_accounts": {"METHODS": ("GET", "POST"),
241 "ROLE_PERMISSION": "wim_accounts:",
242 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
243 "ROLE_PERMISSION": "wim_accounts:id:"
246 "sdns": {"METHODS": ("GET", "POST"),
247 "ROLE_PERMISSION": "sdn_controllers:",
248 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
249 "ROLE_PERMISSION": "sdn_controllers:id:"
256 "pdu_descriptors": {"METHODS": ("GET", "POST"),
257 "ROLE_PERMISSION": "pduds:",
258 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
259 "ROLE_PERMISSION": "pduds:id:"
266 "ns_descriptors_content": {"METHODS": ("GET", "POST"),
267 "ROLE_PERMISSION": "nsds:",
268 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
269 "ROLE_PERMISSION": "nsds:id:"
272 "ns_descriptors": {"METHODS": ("GET", "POST"),
273 "ROLE_PERMISSION": "nsds:",
274 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
275 "ROLE_PERMISSION": "nsds:id:",
276 "nsd_content": {"METHODS": ("GET", "PUT"),
277 "ROLE_PERMISSION": "nsds:id:content:",
279 "nsd": {"METHODS": ("GET",), # descriptor inside package
280 "ROLE_PERMISSION": "nsds:id:content:"
282 "artifacts": {"*": {"METHODS": ("GET",),
283 "ROLE_PERMISSION": "nsds:id:nsd_artifact:"
288 "pnf_descriptors": {"TODO": ("GET", "POST"),
289 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
290 "pnfd_content": {"TODO": ("GET", "PUT")}
293 "subscriptions": {"TODO": ("GET", "POST"),
294 "<ID>": {"TODO": ("GET", "DELETE")}
300 "vnf_packages_content": {"METHODS": ("GET", "POST"),
301 "ROLE_PERMISSION": "vnfds:",
302 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
303 "ROLE_PERMISSION": "vnfds:id:"}
305 "vnf_packages": {"METHODS": ("GET", "POST"),
306 "ROLE_PERMISSION": "vnfds:",
307 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
308 "ROLE_PERMISSION": "vnfds:id:",
309 "package_content": {"METHODS": ("GET", "PUT"), # package
310 "ROLE_PERMISSION": "vnfds:id:",
311 "upload_from_uri": {"METHODS": (),
313 "ROLE_PERMISSION": "vnfds:id:upload:"
316 "vnfd": {"METHODS": ("GET", ), # descriptor inside package
317 "ROLE_PERMISSION": "vnfds:id:content:"
319 "artifacts": {"*": {"METHODS": ("GET", ),
320 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:"
325 "subscriptions": {"TODO": ("GET", "POST"),
326 "<ID>": {"TODO": ("GET", "DELETE")}
332 "ns_instances_content": {"METHODS": ("GET", "POST"),
333 "ROLE_PERMISSION": "ns_instances:",
334 "<ID>": {"METHODS": ("GET", "DELETE"),
335 "ROLE_PERMISSION": "ns_instances:id:"
338 "ns_instances": {"METHODS": ("GET", "POST"),
339 "ROLE_PERMISSION": "ns_instances:",
340 "<ID>": {"METHODS": ("GET", "DELETE"),
341 "ROLE_PERMISSION": "ns_instances:id:",
342 "scale": {"METHODS": ("POST",),
343 "ROLE_PERMISSION": "ns_instances:id:scale:"
345 "terminate": {"METHODS": ("POST",),
346 "ROLE_PERMISSION": "ns_instances:id:terminate:"
348 "instantiate": {"METHODS": ("POST",),
349 "ROLE_PERMISSION": "ns_instances:id:instantiate:"
351 "action": {"METHODS": ("POST",),
352 "ROLE_PERMISSION": "ns_instances:id:action:"
356 "ns_lcm_op_occs": {"METHODS": ("GET",),
357 "ROLE_PERMISSION": "ns_instances:opps:",
358 "<ID>": {"METHODS": ("GET",),
359 "ROLE_PERMISSION": "ns_instances:opps:id:"
362 "vnfrs": {"METHODS": ("GET",),
363 "ROLE_PERMISSION": "vnf_instances:",
364 "<ID>": {"METHODS": ("GET",),
365 "ROLE_PERMISSION": "vnf_instances:id:"
368 "vnf_instances": {"METHODS": ("GET",),
369 "ROLE_PERMISSION": "vnf_instances:",
370 "<ID>": {"METHODS": ("GET",),
371 "ROLE_PERMISSION": "vnf_instances:id:"
378 "netslice_templates_content": {"METHODS": ("GET", "POST"),
379 "ROLE_PERMISSION": "slice_templates:",
380 "<ID>": {"METHODS": ("GET", "PUT", "DELETE"),
381 "ROLE_PERMISSION": "slice_templates:id:", }
383 "netslice_templates": {"METHODS": ("GET", "POST"),
384 "ROLE_PERMISSION": "slice_templates:",
385 "<ID>": {"METHODS": ("GET", "DELETE"),
387 "ROLE_PERMISSION": "slice_templates:id:",
388 "nst_content": {"METHODS": ("GET", "PUT"),
389 "ROLE_PERMISSION": "slice_templates:id:content:"
391 "nst": {"METHODS": ("GET",), # descriptor inside package
392 "ROLE_PERMISSION": "slice_templates:id:content:"
394 "artifacts": {"*": {"METHODS": ("GET",),
395 "ROLE_PERMISSION": "slice_templates:id:content:"
400 "subscriptions": {"TODO": ("GET", "POST"),
401 "<ID>": {"TODO": ("GET", "DELETE")}
407 "netslice_instances_content": {"METHODS": ("GET", "POST"),
408 "ROLE_PERMISSION": "slice_instances:",
409 "<ID>": {"METHODS": ("GET", "DELETE"),
410 "ROLE_PERMISSION": "slice_instances:id:"
413 "netslice_instances": {"METHODS": ("GET", "POST"),
414 "ROLE_PERMISSION": "slice_instances:",
415 "<ID>": {"METHODS": ("GET", "DELETE"),
416 "ROLE_PERMISSION": "slice_instances:id:",
417 "terminate": {"METHODS": ("POST",),
418 "ROLE_PERMISSION": "slice_instances:id:terminate:"
420 "instantiate": {"METHODS": ("POST",),
421 "ROLE_PERMISSION": "slice_instances:id:instantiate:"
423 "action": {"METHODS": ("POST",),
424 "ROLE_PERMISSION": "slice_instances:id:action:"
428 "nsi_lcm_op_occs": {"METHODS": ("GET",),
429 "ROLE_PERMISSION": "slice_instances:opps:",
430 "<ID>": {"METHODS": ("GET",),
431 "ROLE_PERMISSION": "slice_instances:opps:id:",
441 "<ID>": {"METHODS": ("GET",),
442 "ROLE_PERMISSION": "reports:id:",
452 class NbiException(Exception):
454 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
455 Exception.__init
__(self
, message
)
456 self
.http_code
= http_code
459 class Server(object):
461 # to decode bytes to str
462 reader
= getreader("utf-8")
466 self
.engine
= Engine()
467 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
469 def _format_in(self
, kwargs
):
472 if cherrypy
.request
.body
.length
:
473 error_text
= "Invalid input format "
475 if "Content-Type" in cherrypy
.request
.headers
:
476 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
477 error_text
= "Invalid json format "
478 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
479 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
480 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
481 error_text
= "Invalid yaml format "
482 indata
= yaml
.load(cherrypy
.request
.body
)
483 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
484 elif "application/binary" in cherrypy
.request
.headers
["Content-Type"] or \
485 "application/gzip" in cherrypy
.request
.headers
["Content-Type"] or \
486 "application/zip" in cherrypy
.request
.headers
["Content-Type"] or \
487 "text/plain" in cherrypy
.request
.headers
["Content-Type"]:
488 indata
= cherrypy
.request
.body
# .read()
489 elif "multipart/form-data" in cherrypy
.request
.headers
["Content-Type"]:
490 if "descriptor_file" in kwargs
:
491 filecontent
= kwargs
.pop("descriptor_file")
492 if not filecontent
.file:
493 raise NbiException("empty file or content", HTTPStatus
.BAD_REQUEST
)
494 indata
= filecontent
.file # .read()
495 if filecontent
.content_type
.value
:
496 cherrypy
.request
.headers
["Content-Type"] = filecontent
.content_type
.value
498 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
499 # "Only 'Content-Type' of type 'application/json' or
500 # 'application/yaml' for input format are available")
501 error_text
= "Invalid yaml format "
502 indata
= yaml
.load(cherrypy
.request
.body
)
503 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
505 error_text
= "Invalid yaml format "
506 indata
= yaml
.load(cherrypy
.request
.body
)
507 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
512 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
515 for k
, v
in kwargs
.items():
516 if isinstance(v
, str):
521 kwargs
[k
] = yaml
.load(v
)
524 elif k
.endswith(".gt") or k
.endswith(".lt") or k
.endswith(".gte") or k
.endswith(".lte"):
532 elif v
.find(",") > 0:
533 kwargs
[k
] = v
.split(",")
534 elif isinstance(v
, (list, tuple)):
535 for index
in range(0, len(v
)):
540 v
[index
] = yaml
.load(v
[index
])
545 except (ValueError, yaml
.YAMLError
) as exc
:
546 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
547 except KeyError as exc
:
548 raise NbiException("Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
)
549 except Exception as exc
:
550 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
553 def _format_out(data
, token_info
=None, _format
=None):
555 return string of dictionary data according to requested json, yaml, xml. By default json
556 :param data: response to be sent. Can be a dict, text or file
557 :param token_info: Contains among other username and project
558 :param _format: The format to be set as Content-Type ir data is a file
561 accept
= cherrypy
.request
.headers
.get("Accept")
563 if accept
and "text/html" in accept
:
564 return html
.format(data
, cherrypy
.request
, cherrypy
.response
, token_info
)
565 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
567 elif hasattr(data
, "read"): # file object
569 cherrypy
.response
.headers
["Content-Type"] = _format
570 elif "b" in data
.mode
: # binariy asssumig zip
571 cherrypy
.response
.headers
["Content-Type"] = 'application/zip'
573 cherrypy
.response
.headers
["Content-Type"] = 'text/plain'
574 # TODO check that cherrypy close file. If not implement pending things to close per thread next
577 if "application/json" in accept
:
578 cherrypy
.response
.headers
["Content-Type"] = 'application/json; charset=utf-8'
579 a
= json
.dumps(data
, indent
=4) + "\n"
580 return a
.encode("utf8")
581 elif "text/html" in accept
:
582 return html
.format(data
, cherrypy
.request
, cherrypy
.response
, token_info
)
584 elif "application/yaml" in accept
or "*/*" in accept
or "text/plain" in accept
:
586 # if there is not any valid accept, raise an error. But if response is already an error, format in yaml
587 elif cherrypy
.response
.status
>= 400:
588 raise cherrypy
.HTTPError(HTTPStatus
.NOT_ACCEPTABLE
.value
,
589 "Only 'Accept' of type 'application/json' or 'application/yaml' "
590 "for output format are available")
591 cherrypy
.response
.headers
["Content-Type"] = 'application/yaml'
592 return yaml
.safe_dump(data
, explicit_start
=True, indent
=4, default_flow_style
=False, tags
=False,
593 encoding
='utf-8', allow_unicode
=True) # , canonical=True, default_style='"'
596 def index(self
, *args
, **kwargs
):
599 if cherrypy
.request
.method
== "GET":
600 token_info
= self
.authenticator
.authorize()
601 outdata
= "Index page"
603 raise cherrypy
.HTTPError(HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
604 "Method {} not allowed for tokens".format(cherrypy
.request
.method
))
606 return self
._format
_out
(outdata
, token_info
)
608 except (EngineException
, AuthException
) as e
:
609 # cherrypy.log("index Exception {}".format(e))
610 cherrypy
.response
.status
= e
.http_code
.value
611 return self
._format
_out
("Welcome to OSM!", token_info
)
614 def version(self
, *args
, **kwargs
):
615 # TODO consider to remove and provide version using the static version file
616 global __version__
, version_date
618 if cherrypy
.request
.method
!= "GET":
619 raise NbiException("Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
)
621 raise NbiException("Invalid URL or query string for version", HTTPStatus
.METHOD_NOT_ALLOWED
)
622 return __version__
+ " " + version_date
623 except NbiException
as e
:
624 cherrypy
.response
.status
= e
.http_code
.value
626 "code": e
.http_code
.name
,
627 "status": e
.http_code
.value
,
630 return self
._format
_out
(problem_details
, None)
633 def token(self
, method
, token_id
=None, kwargs
=None):
635 # self.engine.load_dbase(cherrypy.request.app.config)
636 indata
= self
._format
_in
(kwargs
)
637 if not isinstance(indata
, dict):
638 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus
.BAD_REQUEST
)
641 token_info
= self
.authenticator
.authorize()
643 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
645 outdata
= self
.authenticator
.get_token_list(token_info
)
646 elif method
== "POST":
648 token_info
= self
.authenticator
.authorize()
652 indata
.update(kwargs
)
653 outdata
= self
.authenticator
.new_token(token_info
, indata
, cherrypy
.request
.remote
)
655 cherrypy
.session
['Authorization'] = outdata
["_id"]
656 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
657 # cherrypy.response.cookie["Authorization"] = outdata["id"]
658 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
659 elif method
== "DELETE":
660 if not token_id
and "id" in kwargs
:
661 token_id
= kwargs
["id"]
663 token_info
= self
.authenticator
.authorize()
664 token_id
= token_info
["_id"]
665 outdata
= self
.authenticator
.del_token(token_id
)
667 cherrypy
.session
['Authorization'] = "logout"
668 # cherrypy.response.cookie["Authorization"] = token_id
669 # cherrypy.response.cookie["Authorization"]['expires'] = 0
671 raise NbiException("Method {} not allowed for token".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
672 return self
._format
_out
(outdata
, token_info
)
673 except (NbiException
, EngineException
, DbException
, AuthException
) as e
:
674 cherrypy
.log("tokens Exception {}".format(e
))
675 cherrypy
.response
.status
= e
.http_code
.value
677 "code": e
.http_code
.name
,
678 "status": e
.http_code
.value
,
681 return self
._format
_out
(problem_details
, token_info
)
684 def test(self
, *args
, **kwargs
):
686 if args
and args
[0] == "help":
687 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"\
688 "sleep/<time>\nmessage/topic\n</pre></html>"
690 elif args
and args
[0] == "init":
692 # self.engine.load_dbase(cherrypy.request.app.config)
693 self
.engine
.create_admin()
694 return "Done. User 'admin', password 'admin' created"
696 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
697 return self
._format
_out
("Database already initialized")
698 elif args
and args
[0] == "file":
699 return cherrypy
.lib
.static
.serve_file(cherrypy
.tree
.apps
['/osm'].config
["storage"]["path"] + "/" + args
[1],
700 "text/plain", "attachment")
701 elif args
and args
[0] == "file2":
702 f_path
= cherrypy
.tree
.apps
['/osm'].config
["storage"]["path"] + "/" + args
[1]
703 f
= open(f_path
, "r")
704 cherrypy
.response
.headers
["Content-type"] = "text/plain"
707 elif len(args
) == 2 and args
[0] == "db-clear":
708 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
709 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
710 elif len(args
) and args
[0] == "fs-clear":
714 folders
= self
.engine
.fs
.dir_ls(".")
715 for folder
in folders
:
716 self
.engine
.fs
.file_delete(folder
)
717 return ",".join(folders
) + " folders deleted\n"
718 elif args
and args
[0] == "login":
719 if not cherrypy
.request
.headers
.get("Authorization"):
720 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
721 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
722 elif args
and args
[0] == "login2":
723 if not cherrypy
.request
.headers
.get("Authorization"):
724 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
725 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
726 elif args
and args
[0] == "sleep":
729 sleep_time
= int(args
[1])
731 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
732 return self
._format
_out
("Database already initialized")
733 thread_info
= cherrypy
.thread_data
735 time
.sleep(sleep_time
)
737 elif len(args
) >= 2 and args
[0] == "message":
739 return_text
= "<html><pre>{} ->\n".format(main_topic
)
741 if cherrypy
.request
.method
== 'POST':
742 to_send
= yaml
.load(cherrypy
.request
.body
)
743 for k
, v
in to_send
.items():
744 self
.engine
.msg
.write(main_topic
, k
, v
)
745 return_text
+= " {}: {}\n".format(k
, v
)
746 elif cherrypy
.request
.method
== 'GET':
747 for k
, v
in kwargs
.items():
748 self
.engine
.msg
.write(main_topic
, k
, yaml
.load(v
))
749 return_text
+= " {}: {}\n".format(k
, yaml
.load(v
))
750 except Exception as e
:
751 return_text
+= "Error: " + str(e
)
752 return_text
+= "</pre></html>\n"
756 "<html><pre>\nheaders:\n args: {}\n".format(args
) +
757 " kwargs: {}\n".format(kwargs
) +
758 " headers: {}\n".format(cherrypy
.request
.headers
) +
759 " path_info: {}\n".format(cherrypy
.request
.path_info
) +
760 " query_string: {}\n".format(cherrypy
.request
.query_string
) +
761 " session: {}\n".format(cherrypy
.session
) +
762 " cookie: {}\n".format(cherrypy
.request
.cookie
) +
763 " method: {}\n".format(cherrypy
.request
.method
) +
764 " session: {}\n".format(cherrypy
.session
.get('fieldname')) +
766 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
767 if cherrypy
.request
.body
.length
:
768 return_text
+= " content: {}\n".format(
769 str(cherrypy
.request
.body
.read(int(cherrypy
.request
.headers
.get('Content-Length', 0)))))
771 return_text
+= "thread: {}\n".format(thread_info
)
772 return_text
+= "</pre></html>"
776 def _check_valid_url_method(method
, *args
):
778 raise NbiException("URL must contain at least 'main_topic/version/topic'", HTTPStatus
.METHOD_NOT_ALLOWED
)
780 reference
= valid_url_methods
784 if not isinstance(reference
, dict):
785 raise NbiException("URL contains unexpected extra items '{}'".format(arg
),
786 HTTPStatus
.METHOD_NOT_ALLOWED
)
789 reference
= reference
[arg
]
790 elif "<ID>" in reference
:
791 reference
= reference
["<ID>"]
792 elif "*" in reference
:
793 reference
= reference
["*"]
796 raise NbiException("Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
)
797 if "TODO" in reference
and method
in reference
["TODO"]:
798 raise NbiException("Method {} not supported yet for this URL".format(method
), HTTPStatus
.NOT_IMPLEMENTED
)
799 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
800 raise NbiException("Method {} not supported for this URL".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
801 return reference
["ROLE_PERMISSION"] + method
.lower()
804 def _set_location_header(main_topic
, version
, topic
, id):
806 Insert response header Location with the URL of created item base on URL params
813 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
814 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(main_topic
, version
, topic
, id)
818 def _extract_query_string_operations(kwargs
, method
):
824 query_string_operations
= []
826 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
827 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
828 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
829 return query_string_operations
832 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
834 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
835 Check that users has rights to use them and returs the admin_query
836 :param token_info: token_info rights obtained by token
837 :param kwargs: query string input.
838 :param method: http method: GET, POSST, PUT, ...
840 :return: admin_query dictionary with keys:
841 public: True, False or None
843 project_id: tuple with projects used for accessing an element
844 set_project: tuple with projects that a created element will belong to
845 method: show, list, delete, write
847 admin_query
= {"force": False, "project_id": (token_info
["project_id"], ), "username": token_info
["username"],
848 "admin": token_info
["admin"], "public": None}
851 if "FORCE" in kwargs
:
852 if kwargs
["FORCE"].lower() != "false": # if None or True set force to True
853 admin_query
["force"] = True
856 if "PUBLIC" in kwargs
:
857 if kwargs
["PUBLIC"].lower() != "false": # if None or True set public to True
858 admin_query
["public"] = True
860 admin_query
["public"] = False
863 if "ADMIN" in kwargs
:
864 behave_as
= kwargs
.pop("ADMIN")
865 if behave_as
.lower() != "false":
866 if not token_info
["admin"]:
867 raise NbiException("Only admin projects can use 'ADMIN' query string", HTTPStatus
.UNAUTHORIZED
)
868 if not behave_as
or behave_as
.lower() == "true": # convert True, None to empty list
869 admin_query
["project_id"] = ()
870 elif isinstance(behave_as
, (list, tuple)):
871 admin_query
["project_id"] = behave_as
872 else: # isinstance(behave_as, str)
873 admin_query
["project_id"] = (behave_as
, )
874 if "SET_PROJECT" in kwargs
:
875 set_project
= kwargs
.pop("SET_PROJECT")
877 admin_query
["set_project"] = list(admin_query
["project_id"])
879 if isinstance(set_project
, str):
880 set_project
= (set_project
, )
881 if admin_query
["project_id"]:
882 for p
in set_project
:
883 if p
not in admin_query
["project_id"]:
884 raise NbiException("Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
885 "'ADMIN='{p}'".format(p
=p
), HTTPStatus
.UNAUTHORIZED
)
886 admin_query
["set_project"] = set_project
889 # if "PROJECT_READ" in kwargs:
890 # admin_query["project"] = kwargs.pop("project")
891 # if admin_query["project"] == token_info["project_id"]:
894 admin_query
["method"] = "show"
896 admin_query
["method"] = "list"
897 elif method
== "DELETE":
898 admin_query
["method"] = "delete"
900 admin_query
["method"] = "write"
904 def default(self
, main_topic
=None, version
=None, topic
=None, _id
=None, item
=None, *args
, **kwargs
):
911 engine_session
= None
913 if not main_topic
or not version
or not topic
:
914 raise NbiException("URL must contain at least 'main_topic/version/topic'",
915 HTTPStatus
.METHOD_NOT_ALLOWED
)
916 if main_topic
not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm", "nspm"):
917 raise NbiException("URL main_topic '{}' not supported".format(main_topic
),
918 HTTPStatus
.METHOD_NOT_ALLOWED
)
920 raise NbiException("URL version '{}' not supported".format(version
), HTTPStatus
.METHOD_NOT_ALLOWED
)
922 if kwargs
and "METHOD" in kwargs
and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
923 method
= kwargs
.pop("METHOD")
925 method
= cherrypy
.request
.method
927 role_permission
= self
._check
_valid
_url
_method
(method
, main_topic
, version
, topic
, _id
, item
, *args
)
928 query_string_operations
= self
._extract
_query
_string
_operations
(kwargs
, method
)
929 if main_topic
== "admin" and topic
== "tokens":
930 return self
.token(method
, _id
, kwargs
)
932 # self.engine.load_dbase(cherrypy.request.app.config)
934 token_info
= self
.authenticator
.authorize(role_permission
, query_string_operations
)
935 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
936 indata
= self
._format
_in
(kwargs
)
938 if topic
== "subscriptions":
939 engine_topic
= main_topic
+ "_" + topic
940 if item
and topic
!= "pm_jobs":
943 if main_topic
== "nsd":
944 engine_topic
= "nsds"
945 elif main_topic
== "vnfpkgm":
946 engine_topic
= "vnfds"
947 elif main_topic
== "nslcm":
948 engine_topic
= "nsrs"
949 if topic
== "ns_lcm_op_occs":
950 engine_topic
= "nslcmops"
951 if topic
== "vnfrs" or topic
== "vnf_instances":
952 engine_topic
= "vnfrs"
953 elif main_topic
== "nst":
954 engine_topic
= "nsts"
955 elif main_topic
== "nsilcm":
956 engine_topic
= "nsis"
957 if topic
== "nsi_lcm_op_occs":
958 engine_topic
= "nsilcmops"
959 elif main_topic
== "pdu":
960 engine_topic
= "pdus"
961 if engine_topic
== "vims": # TODO this is for backward compatibility, it will be removed in the future
962 engine_topic
= "vim_accounts"
965 if item
in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
966 if item
in ("vnfd", "nsd", "nst"):
970 elif item
== "artifacts":
974 file, _format
= self
.engine
.get_file(engine_session
, engine_topic
, _id
, path
,
975 cherrypy
.request
.headers
.get("Accept"))
978 outdata
= self
.engine
.get_item_list(engine_session
, engine_topic
, kwargs
)
980 if item
== "reports":
981 # TODO check that project_id (_id in this context) has permissions
983 outdata
= self
.engine
.get_item(engine_session
, engine_topic
, _id
)
984 elif method
== "POST":
985 if topic
in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"):
986 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
988 _id
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, {}, None,
989 cherrypy
.request
.headers
)
990 completed
= self
.engine
.upload_content(engine_session
, engine_topic
, _id
, indata
, kwargs
,
991 cherrypy
.request
.headers
)
993 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
995 cherrypy
.response
.headers
["Transaction-Id"] = _id
996 outdata
= {"id": _id
}
997 elif topic
== "ns_instances_content":
999 _id
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, indata
, kwargs
)
1001 indata
["lcmOperationType"] = "instantiate"
1002 indata
["nsInstanceId"] = _id
1003 nslcmop_id
= self
.engine
.new_item(rollback
, engine_session
, "nslcmops", indata
, None)
1004 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1005 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1006 elif topic
== "ns_instances" and item
:
1007 indata
["lcmOperationType"] = item
1008 indata
["nsInstanceId"] = _id
1009 _id
= self
.engine
.new_item(rollback
, engine_session
, "nslcmops", indata
, kwargs
)
1010 self
._set
_location
_header
(main_topic
, version
, "ns_lcm_op_occs", _id
)
1011 outdata
= {"id": _id
}
1012 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1013 elif topic
== "netslice_instances_content":
1014 # creates NetSlice_Instance_record (NSIR)
1015 _id
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, indata
, kwargs
)
1016 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1017 indata
["lcmOperationType"] = "instantiate"
1018 indata
["netsliceInstanceId"] = _id
1019 nsilcmop_id
= self
.engine
.new_item(rollback
, engine_session
, "nsilcmops", indata
, kwargs
)
1020 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1022 elif topic
== "netslice_instances" and item
:
1023 indata
["lcmOperationType"] = item
1024 indata
["netsliceInstanceId"] = _id
1025 _id
= self
.engine
.new_item(rollback
, engine_session
, "nsilcmops", indata
, kwargs
)
1026 self
._set
_location
_header
(main_topic
, version
, "nsi_lcm_op_occs", _id
)
1027 outdata
= {"id": _id
}
1028 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1030 _id
= self
.engine
.new_item(rollback
, engine_session
, engine_topic
, indata
, kwargs
,
1031 cherrypy
.request
.headers
)
1032 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1033 outdata
= {"id": _id
}
1034 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1035 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1037 elif method
== "DELETE":
1039 outdata
= self
.engine
.del_item_list(engine_session
, engine_topic
, kwargs
)
1040 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1041 else: # len(args) > 1
1042 delete_in_process
= False
1043 if topic
== "ns_instances_content" and not engine_session
["force"]:
1045 "lcmOperationType": "terminate",
1046 "nsInstanceId": _id
,
1049 opp_id
= self
.engine
.new_item(rollback
, engine_session
, "nslcmops", nslcmop_desc
, None)
1051 delete_in_process
= True
1052 outdata
= {"_id": opp_id
}
1053 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1054 elif topic
== "netslice_instances_content" and not engine_session
["force"]:
1056 "lcmOperationType": "terminate",
1057 "netsliceInstanceId": _id
,
1060 opp_id
= self
.engine
.new_item(rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None)
1062 delete_in_process
= True
1063 outdata
= {"_id": opp_id
}
1064 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1065 if not delete_in_process
:
1066 self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1067 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1068 if engine_topic
in ("vim_accounts", "wim_accounts", "sdns"):
1069 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1071 elif method
in ("PUT", "PATCH"):
1073 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1074 raise NbiException("Nothing to update. Provide payload and/or query string",
1075 HTTPStatus
.BAD_REQUEST
)
1076 if item
in ("nsd_content", "package_content", "nst_content") and method
== "PUT":
1077 completed
= self
.engine
.upload_content(engine_session
, engine_topic
, _id
, indata
, kwargs
,
1078 cherrypy
.request
.headers
)
1080 cherrypy
.response
.headers
["Transaction-Id"] = id
1082 self
.engine
.edit_item(engine_session
, engine_topic
, _id
, indata
, kwargs
)
1083 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1085 raise NbiException("Method {} not allowed".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
1087 # if Role information changes, it is needed to reload the information of roles
1088 if topic
== "roles" and method
!= "GET":
1089 self
.authenticator
.load_operation_to_allowed_roles()
1090 return self
._format
_out
(outdata
, token_info
, _format
)
1091 except Exception as e
:
1092 if isinstance(e
, (NbiException
, EngineException
, DbException
, FsException
, MsgException
, AuthException
,
1094 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1095 http_code_name
= e
.http_code
.name
1096 cherrypy
.log("Exception {}".format(e
))
1098 http_code_value
= cherrypy
.response
.status
= HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1099 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1100 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1101 if hasattr(outdata
, "close"): # is an open file
1105 for rollback_item
in rollback
:
1107 if rollback_item
.get("operation") == "set":
1108 self
.engine
.db
.set_one(rollback_item
["topic"], {"_id": rollback_item
["_id"]},
1109 rollback_item
["content"], fail_on_empty
=False)
1111 self
.engine
.db
.del_one(rollback_item
["topic"], {"_id": rollback_item
["_id"]},
1112 fail_on_empty
=False)
1113 except Exception as e2
:
1114 rollback_error_text
= "Rollback Exception {}: {}".format(rollback_item
, e2
)
1115 cherrypy
.log(rollback_error_text
)
1116 error_text
+= ". " + rollback_error_text
1117 # if isinstance(e, MsgException):
1118 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1119 # engine_topic[:-1], method, error_text)
1121 "code": http_code_name
,
1122 "status": http_code_value
,
1123 "detail": error_text
,
1125 return self
._format
_out
(problem_details
, token_info
)
1126 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1129 def _start_service():
1131 Callback function called when cherrypy.engine starts
1132 Override configuration with env variables
1133 Set database, storage, message configuration
1134 Init database with admin/admin user password
1137 global subscription_thread
1138 cherrypy
.log
.error("Starting osm_nbi")
1139 # update general cherrypy configuration
1142 engine_config
= cherrypy
.tree
.apps
['/osm'].config
1143 for k
, v
in environ
.items():
1144 if not k
.startswith("OSMNBI_"):
1146 k1
, _
, k2
= k
[7:].lower().partition("_")
1150 # update static configuration
1151 if k
== 'OSMNBI_STATIC_DIR':
1152 engine_config
["/static"]['tools.staticdir.dir'] = v
1153 engine_config
["/static"]['tools.staticdir.on'] = True
1154 elif k
== 'OSMNBI_SOCKET_PORT' or k
== 'OSMNBI_SERVER_PORT':
1155 update_dict
['server.socket_port'] = int(v
)
1156 elif k
== 'OSMNBI_SOCKET_HOST' or k
== 'OSMNBI_SERVER_HOST':
1157 update_dict
['server.socket_host'] = v
1158 elif k1
in ("server", "test", "auth", "log"):
1159 update_dict
[k1
+ '.' + k2
] = v
1160 elif k1
in ("message", "database", "storage", "authentication"):
1161 # k2 = k2.replace('_', '.')
1162 if k2
in ("port", "db_port"):
1163 engine_config
[k1
][k2
] = int(v
)
1165 engine_config
[k1
][k2
] = v
1167 except ValueError as e
:
1168 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1169 except Exception as e
:
1170 cherrypy
.log
.warn("skipping environ '{}' on exception '{}'".format(k
, e
))
1173 cherrypy
.config
.update(update_dict
)
1174 engine_config
["global"].update(update_dict
)
1177 log_format_simple
= "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1178 log_formatter_simple
= logging
.Formatter(log_format_simple
, datefmt
='%Y-%m-%dT%H:%M:%S')
1179 logger_server
= logging
.getLogger("cherrypy.error")
1180 logger_access
= logging
.getLogger("cherrypy.access")
1181 logger_cherry
= logging
.getLogger("cherrypy")
1182 logger_nbi
= logging
.getLogger("nbi")
1184 if "log.file" in engine_config
["global"]:
1185 file_handler
= logging
.handlers
.RotatingFileHandler(engine_config
["global"]["log.file"],
1186 maxBytes
=100e6
, backupCount
=9, delay
=0)
1187 file_handler
.setFormatter(log_formatter_simple
)
1188 logger_cherry
.addHandler(file_handler
)
1189 logger_nbi
.addHandler(file_handler
)
1190 # log always to standard output
1191 for format_
, logger
in {"nbi.server %(filename)s:%(lineno)s": logger_server
,
1192 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1193 "%(name)s %(filename)s:%(lineno)s": logger_nbi
1195 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1196 log_formatter_cherry
= logging
.Formatter(log_format_cherry
, datefmt
='%Y-%m-%dT%H:%M:%S')
1197 str_handler
= logging
.StreamHandler()
1198 str_handler
.setFormatter(log_formatter_cherry
)
1199 logger
.addHandler(str_handler
)
1201 if engine_config
["global"].get("log.level"):
1202 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1203 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1205 # logging other modules
1206 for k1
, logname
in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
1207 engine_config
[k1
]["logger_name"] = logname
1208 logger_module
= logging
.getLogger(logname
)
1209 if "logfile" in engine_config
[k1
]:
1210 file_handler
= logging
.handlers
.RotatingFileHandler(engine_config
[k1
]["logfile"],
1211 maxBytes
=100e6
, backupCount
=9, delay
=0)
1212 file_handler
.setFormatter(log_formatter_simple
)
1213 logger_module
.addHandler(file_handler
)
1214 if "loglevel" in engine_config
[k1
]:
1215 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1216 # TODO add more entries, e.g.: storage
1217 cherrypy
.tree
.apps
['/osm'].root
.engine
.start(engine_config
)
1218 cherrypy
.tree
.apps
['/osm'].root
.authenticator
.start(engine_config
)
1219 cherrypy
.tree
.apps
['/osm'].root
.engine
.init_db(target_version
=database_version
)
1220 cherrypy
.tree
.apps
['/osm'].root
.authenticator
.init_db(target_version
=auth_database_version
)
1222 # start subscriptions thread:
1223 subscription_thread
= SubscriptionThread(config
=engine_config
, engine
=nbi_server
.engine
)
1224 subscription_thread
.start()
1225 # Do not capture except SubscriptionException
1227 # load and print version. Ignore possible errors, e.g. file not found
1229 with
open("{}/version".format(engine_config
["/static"]['tools.staticdir.dir'])) as version_file
:
1230 version_data
= version_file
.read()
1231 cherrypy
.log
.error("Starting OSM NBI Version: {}".format(version_data
.replace("\n", " ")))
1236 def _stop_service():
1238 Callback function called when cherrypy.engine stops
1239 TODO: Ending database connections.
1241 global subscription_thread
1242 if subscription_thread
:
1243 subscription_thread
.terminate()
1244 subscription_thread
= None
1245 cherrypy
.tree
.apps
['/osm'].root
.engine
.stop()
1246 cherrypy
.log
.error("Stopping osm_nbi")
1249 def nbi(config_file
):
1253 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1254 # 'tools.sessions.on': True,
1255 # 'tools.response_headers.on': True,
1256 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1259 # cherrypy.Server.ssl_module = 'builtin'
1260 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1261 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1262 # cherrypy.Server.thread_pool = 10
1263 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1265 # cherrypy.config.update({'tools.auth_basic.on': True,
1266 # 'tools.auth_basic.realm': 'localhost',
1267 # 'tools.auth_basic.checkpassword': validate_password})
1268 nbi_server
= Server()
1269 cherrypy
.engine
.subscribe('start', _start_service
)
1270 cherrypy
.engine
.subscribe('stop', _stop_service
)
1271 cherrypy
.quickstart(nbi_server
, '/osm', config_file
)
1275 print("""Usage: {} [options]
1276 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1277 -h|--help: shows this help
1278 """.format(sys
.argv
[0]))
1279 # --log-socket-host HOST: send logs to this host")
1280 # --log-socket-port PORT: send logs using this port (default: 9022)")
1283 if __name__
== '__main__':
1285 # load parameters and configuration
1286 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
1287 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1290 if o
in ("-h", "--help"):
1293 elif o
in ("-c", "--config"):
1295 # elif o == "--log-socket-port":
1296 # log_socket_port = a
1297 # elif o == "--log-socket-host":
1298 # log_socket_host = a
1299 # elif o == "--log-file":
1302 assert False, "Unhandled option"
1304 if not path
.isfile(config_file
):
1305 print("configuration file '{}' that not exist".format(config_file
), file=sys
.stderr
)
1308 for config_file
in (__file__
[:__file__
.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
1309 if path
.isfile(config_file
):
1312 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys
.stderr
)
1315 except getopt
.GetoptError
as e
:
1316 print(str(e
), file=sys
.stderr
)