de48c033e1b9d8a4fe97a63f58c403f871f13e26
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
94 /vnf_instances (also vnfrs for compatibility) O
110 /vim_accounts (also vims for compatibility) O O
124 /netslice_templates_content O O
126 /netslice_templates O O
130 /artifacts[/<artifactPath>] O
132 /<subscriptionId> X X
135 /netslice_instances_content O O
136 /<SliceInstanceId> O O
137 /netslice_instances O O
138 /<SliceInstanceId> O O
143 /<nsiLcmOpOccId> O O O
145 /<subscriptionId> X X
148 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
149 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
150 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
151 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
153 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
154 item of the array, that is, pass if any item of the array pass the filter.
155 It allows both ne and neq for not equal
156 TODO: 4.3.3 Attribute selectors
157 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
158 (none) … same as “exclude_default”
159 all_fields … all attributes.
160 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
161 conditionally mandatory, and that are not provided in <list>.
162 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
163 are not conditionally mandatory, and that are provided in <list>.
164 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
165 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
166 the particular resource
167 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
168 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
169 present specification for the particular resource, but that are not part of <list>
170 Additionally it admits some administrator values:
171 FORCE: To force operations skipping dependency checkings
172 ADMIN: To act as an administrator or a different project
173 PUBLIC: To get public descriptors or set a descriptor as public
174 SET_PROJECT: To make a descriptor available for other project
176 Header field name Reference Example Descriptions
177 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
178 This header field shall be present if the response is expected to have a non-empty message body.
179 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
180 This header field shall be present if the request has a non-empty message body.
181 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
182 Details are specified in clause 4.5.3.
183 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
184 Header field name Reference Example Descriptions
185 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
186 This header field shall be present if the response has a non-empty message body.
187 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
188 new resource has been created.
189 This header field shall be present if the response status code is 201 or 3xx.
190 In the present document this header field is also used if the response status code is 202 and a new resource was
192 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
193 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
195 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
197 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
198 response, and the total length of the file.
199 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
202 valid_query_string
= ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
203 # ^ Contains possible administrative query string words:
204 # ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
205 # (not owned by my session project).
206 # PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
207 # FORCE=True(by default)|False: Force edition/deletion operations
208 # SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
210 valid_url_methods
= {
211 # contains allowed URL and methods, and the role_permission name
215 "METHODS": ("GET", "POST", "DELETE"),
216 "ROLE_PERMISSION": "tokens:",
217 "<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"},
220 "METHODS": ("GET", "POST"),
221 "ROLE_PERMISSION": "users:",
223 "METHODS": ("GET", "DELETE", "PATCH"),
224 "ROLE_PERMISSION": "users:id:",
228 "METHODS": ("GET", "POST"),
229 "ROLE_PERMISSION": "projects:",
231 "METHODS": ("GET", "DELETE", "PATCH"),
232 "ROLE_PERMISSION": "projects:id:",
236 "METHODS": ("GET", "POST"),
237 "ROLE_PERMISSION": "roles:",
239 "METHODS": ("GET", "DELETE", "PATCH"),
240 "ROLE_PERMISSION": "roles:id:",
244 "METHODS": ("GET", "POST"),
245 "ROLE_PERMISSION": "vims:",
247 "METHODS": ("GET", "DELETE", "PATCH"),
248 "ROLE_PERMISSION": "vims:id:",
252 "METHODS": ("GET", "POST"),
253 "ROLE_PERMISSION": "vim_accounts:",
255 "METHODS": ("GET", "DELETE", "PATCH"),
256 "ROLE_PERMISSION": "vim_accounts:id:",
260 "METHODS": ("GET", "POST"),
261 "ROLE_PERMISSION": "wim_accounts:",
263 "METHODS": ("GET", "DELETE", "PATCH"),
264 "ROLE_PERMISSION": "wim_accounts:id:",
268 "METHODS": ("GET", "POST"),
269 "ROLE_PERMISSION": "sdn_controllers:",
271 "METHODS": ("GET", "DELETE", "PATCH"),
272 "ROLE_PERMISSION": "sdn_controllers:id:",
276 "METHODS": ("GET", "POST"),
277 "ROLE_PERMISSION": "k8sclusters:",
279 "METHODS": ("GET", "DELETE", "PATCH"),
280 "ROLE_PERMISSION": "k8sclusters:id:",
284 "METHODS": ("GET", "POST"),
285 "ROLE_PERMISSION": "vca:",
287 "METHODS": ("GET", "DELETE", "PATCH"),
288 "ROLE_PERMISSION": "vca:id:",
292 "METHODS": ("GET", "POST"),
293 "ROLE_PERMISSION": "k8srepos:",
295 "METHODS": ("GET", "DELETE"),
296 "ROLE_PERMISSION": "k8srepos:id:",
300 "METHODS": ("GET", "POST"),
301 "ROLE_PERMISSION": "osmrepos:",
303 "METHODS": ("GET", "DELETE", "PATCH"),
304 "ROLE_PERMISSION": "osmrepos:id:",
309 "ROLE_PERMISSION": "domains:",
316 "METHODS": ("GET", "POST"),
317 "ROLE_PERMISSION": "pduds:",
319 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
320 "ROLE_PERMISSION": "pduds:id:",
327 "ns_descriptors_content": {
328 "METHODS": ("GET", "POST"),
329 "ROLE_PERMISSION": "nsds:",
331 "METHODS": ("GET", "PUT", "DELETE"),
332 "ROLE_PERMISSION": "nsds:id:",
336 "METHODS": ("GET", "POST"),
337 "ROLE_PERMISSION": "nsds:",
339 "METHODS": ("GET", "DELETE", "PATCH"),
340 "ROLE_PERMISSION": "nsds:id:",
342 "METHODS": ("GET", "PUT"),
343 "ROLE_PERMISSION": "nsds:id:content:",
346 "METHODS": ("GET",), # descriptor inside package
347 "ROLE_PERMISSION": "nsds:id:content:",
351 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
357 "TODO": ("GET", "POST"),
359 "TODO": ("GET", "DELETE", "PATCH"),
360 "pnfd_content": {"TODO": ("GET", "PUT")},
364 "TODO": ("GET", "POST"),
365 "<ID>": {"TODO": ("GET", "DELETE")},
371 "vnf_packages_content": {
372 "METHODS": ("GET", "POST"),
373 "ROLE_PERMISSION": "vnfds:",
375 "METHODS": ("GET", "PUT", "DELETE"),
376 "ROLE_PERMISSION": "vnfds:id:",
380 "METHODS": ("GET", "POST"),
381 "ROLE_PERMISSION": "vnfds:",
383 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
384 "ROLE_PERMISSION": "vnfds:id:",
386 "METHODS": ("GET", "PUT"), # package
387 "ROLE_PERMISSION": "vnfds:id:",
391 "ROLE_PERMISSION": "vnfds:id:upload:",
395 "METHODS": ("GET",), # descriptor inside package
396 "ROLE_PERMISSION": "vnfds:id:content:",
400 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
404 "METHODS": ("POST",),
405 "ROLE_PERMISSION": "vnfds:id:action:",
410 "TODO": ("GET", "POST"),
411 "<ID>": {"TODO": ("GET", "DELETE")},
415 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
416 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"},
422 "ns_instances_content": {
423 "METHODS": ("GET", "POST"),
424 "ROLE_PERMISSION": "ns_instances:",
426 "METHODS": ("GET", "DELETE"),
427 "ROLE_PERMISSION": "ns_instances:id:",
431 "METHODS": ("GET", "POST"),
432 "ROLE_PERMISSION": "ns_instances:",
434 "METHODS": ("GET", "DELETE"),
435 "ROLE_PERMISSION": "ns_instances:id:",
437 "METHODS": ("POST",),
438 "ROLE_PERMISSION": "ns_instances:id:heal:",
441 "METHODS": ("POST",),
442 "ROLE_PERMISSION": "ns_instances:id:scale:",
445 "METHODS": ("POST",),
446 "ROLE_PERMISSION": "ns_instances:id:terminate:",
449 "METHODS": ("POST",),
450 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
453 "METHODS": ("POST",),
454 "ROLE_PERMISSION": "ns_instances:id:migrate:",
457 "METHODS": ("POST",),
458 "ROLE_PERMISSION": "ns_instances:id:action:",
461 "METHODS": ("POST",),
462 "ROLE_PERMISSION": "ns_instances:id:update:",
468 "ROLE_PERMISSION": "ns_instances:opps:",
471 "ROLE_PERMISSION": "ns_instances:opps:id:",
476 "ROLE_PERMISSION": "vnf_instances:",
477 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
481 "ROLE_PERMISSION": "vnf_instances:",
482 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
485 "METHODS": ("GET", "POST"),
486 "ROLE_PERMISSION": "ns_subscriptions:",
488 "METHODS": ("GET", "DELETE"),
489 "ROLE_PERMISSION": "ns_subscriptions:id:",
496 "vnf_instances": {"METHODS": ("GET", "POST"),
497 "ROLE_PERMISSION": "vnflcm_instances:",
498 "<ID>": {"METHODS": ("GET", "DELETE"),
499 "ROLE_PERMISSION": "vnflcm_instances:id:",
500 "scale": {"METHODS": ("POST",),
501 "ROLE_PERMISSION": "vnflcm_instances:id:scale:"
503 "terminate": {"METHODS": ("POST",),
504 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:"
506 "instantiate": {"METHODS": ("POST",),
507 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:"
511 "vnf_lcm_op_occs": {"METHODS": ("GET",),
512 "ROLE_PERMISSION": "vnf_instances:opps:",
513 "<ID>": {"METHODS": ("GET",),
514 "ROLE_PERMISSION": "vnf_instances:opps:id:"
517 "subscriptions": {"METHODS": ("GET", "POST"),
518 "ROLE_PERMISSION": "vnflcm_subscriptions:",
519 "<ID>": {"METHODS": ("GET", "DELETE"),
520 "ROLE_PERMISSION": "vnflcm_subscriptions:id:"
527 "netslice_templates_content": {
528 "METHODS": ("GET", "POST"),
529 "ROLE_PERMISSION": "slice_templates:",
531 "METHODS": ("GET", "PUT", "DELETE"),
532 "ROLE_PERMISSION": "slice_templates:id:",
535 "netslice_templates": {
536 "METHODS": ("GET", "POST"),
537 "ROLE_PERMISSION": "slice_templates:",
539 "METHODS": ("GET", "DELETE"),
541 "ROLE_PERMISSION": "slice_templates:id:",
543 "METHODS": ("GET", "PUT"),
544 "ROLE_PERMISSION": "slice_templates:id:content:",
547 "METHODS": ("GET",), # descriptor inside package
548 "ROLE_PERMISSION": "slice_templates:id:content:",
552 "ROLE_PERMISSION": "slice_templates:id:content:",
558 "TODO": ("GET", "POST"),
559 "<ID>": {"TODO": ("GET", "DELETE")},
565 "netslice_instances_content": {
566 "METHODS": ("GET", "POST"),
567 "ROLE_PERMISSION": "slice_instances:",
569 "METHODS": ("GET", "DELETE"),
570 "ROLE_PERMISSION": "slice_instances:id:",
573 "netslice_instances": {
574 "METHODS": ("GET", "POST"),
575 "ROLE_PERMISSION": "slice_instances:",
577 "METHODS": ("GET", "DELETE"),
578 "ROLE_PERMISSION": "slice_instances:id:",
580 "METHODS": ("POST",),
581 "ROLE_PERMISSION": "slice_instances:id:terminate:",
584 "METHODS": ("POST",),
585 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
588 "METHODS": ("POST",),
589 "ROLE_PERMISSION": "slice_instances:id:action:",
595 "ROLE_PERMISSION": "slice_instances:opps:",
598 "ROLE_PERMISSION": "slice_instances:opps:id:",
610 "ROLE_PERMISSION": "reports:id:",
619 "alarms": {"METHODS": ("GET", "PATCH"),
620 "ROLE_PERMISSION": "alarms:",
621 "<ID>": {"METHODS": ("GET", "PATCH"),
622 "ROLE_PERMISSION": "alarms:id:",
630 class NbiException(Exception):
631 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
632 Exception.__init
__(self
, message
)
633 self
.http_code
= http_code
636 class Server(object):
638 # to decode bytes to str
639 reader
= getreader("utf-8")
643 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
644 self
.engine
= Engine(self
.authenticator
)
646 def _format_in(self
, kwargs
):
649 if cherrypy
.request
.body
.length
:
650 error_text
= "Invalid input format "
652 if "Content-Type" in cherrypy
.request
.headers
:
653 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
654 error_text
= "Invalid json format "
655 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
656 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
657 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
658 error_text
= "Invalid yaml format "
660 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
662 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
664 "application/binary" in cherrypy
.request
.headers
["Content-Type"]
665 or "application/gzip"
666 in cherrypy
.request
.headers
["Content-Type"]
667 or "application/zip" in cherrypy
.request
.headers
["Content-Type"]
668 or "text/plain" in cherrypy
.request
.headers
["Content-Type"]
670 indata
= cherrypy
.request
.body
# .read()
672 "multipart/form-data"
673 in cherrypy
.request
.headers
["Content-Type"]
675 if "descriptor_file" in kwargs
:
676 filecontent
= kwargs
.pop("descriptor_file")
677 if not filecontent
.file:
679 "empty file or content", HTTPStatus
.BAD_REQUEST
681 indata
= filecontent
.file # .read()
682 if filecontent
.content_type
.value
:
683 cherrypy
.request
.headers
[
685 ] = filecontent
.content_type
.value
687 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
688 # "Only 'Content-Type' of type 'application/json' or
689 # 'application/yaml' for input format are available")
690 error_text
= "Invalid yaml format "
692 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
694 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
696 error_text
= "Invalid yaml format "
697 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
698 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
703 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
706 for k
, v
in kwargs
.items():
707 if isinstance(v
, str):
712 kwargs
[k
] = yaml
.load(v
, Loader
=yaml
.SafeLoader
)
718 or k
.endswith(".gte")
719 or k
.endswith(".lte")
728 elif v
.find(",") > 0:
729 kwargs
[k
] = v
.split(",")
730 elif isinstance(v
, (list, tuple)):
731 for index
in range(0, len(v
)):
736 v
[index
] = yaml
.load(v
[index
], Loader
=yaml
.SafeLoader
)
741 except (ValueError, yaml
.YAMLError
) as exc
:
742 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
743 except KeyError as exc
:
745 "Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
747 except Exception as exc
:
748 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
751 def _format_out(data
, token_info
=None, _format
=None):
753 return string of dictionary data according to requested json, yaml, xml. By default json
754 :param data: response to be sent. Can be a dict, text or file
755 :param token_info: Contains among other username and project
756 :param _format: The format to be set as Content-Type if data is a file
759 accept
= cherrypy
.request
.headers
.get("Accept")
761 if accept
and "text/html" in accept
:
763 data
, cherrypy
.request
, cherrypy
.response
, token_info
765 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
767 elif hasattr(data
, "read"): # file object
769 cherrypy
.response
.headers
["Content-Type"] = _format
770 elif "b" in data
.mode
: # binariy asssumig zip
771 cherrypy
.response
.headers
["Content-Type"] = "application/zip"
773 cherrypy
.response
.headers
["Content-Type"] = "text/plain"
774 # TODO check that cherrypy close file. If not implement pending things to close per thread next
777 if "text/html" in accept
:
779 data
, cherrypy
.request
, cherrypy
.response
, token_info
781 elif "application/yaml" in accept
or "*/*" in accept
:
783 elif "application/json" in accept
or (
784 cherrypy
.response
.status
and cherrypy
.response
.status
>= 300
786 cherrypy
.response
.headers
[
788 ] = "application/json; charset=utf-8"
789 a
= json
.dumps(data
, indent
=4) + "\n"
790 return a
.encode("utf8")
791 cherrypy
.response
.headers
["Content-Type"] = "application/yaml"
792 return yaml
.safe_dump(
796 default_flow_style
=False,
800 ) # , canonical=True, default_style='"'
803 def index(self
, *args
, **kwargs
):
806 if cherrypy
.request
.method
== "GET":
807 token_info
= self
.authenticator
.authorize()
808 outdata
= token_info
# Home page
810 raise cherrypy
.HTTPError(
811 HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
812 "Method {} not allowed for tokens".format(cherrypy
.request
.method
),
815 return self
._format
_out
(outdata
, token_info
)
817 except (EngineException
, AuthException
) as e
:
818 # cherrypy.log("index Exception {}".format(e))
819 cherrypy
.response
.status
= e
.http_code
.value
820 return self
._format
_out
("Welcome to OSM!", token_info
)
823 def version(self
, *args
, **kwargs
):
824 # TODO consider to remove and provide version using the static version file
826 if cherrypy
.request
.method
!= "GET":
828 "Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
832 "Invalid URL or query string for version",
833 HTTPStatus
.METHOD_NOT_ALLOWED
,
835 # TODO include version of other modules, pick up from some kafka admin message
836 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
837 return self
._format
_out
(osm_nbi_version
)
838 except NbiException
as e
:
839 cherrypy
.response
.status
= e
.http_code
.value
841 "code": e
.http_code
.name
,
842 "status": e
.http_code
.value
,
845 return self
._format
_out
(problem_details
, None)
850 "user_domain_name": cherrypy
.tree
.apps
["/osm"]
851 .config
["authentication"]
852 .get("user_domain_name"),
853 "project_domain_name": cherrypy
.tree
.apps
["/osm"]
854 .config
["authentication"]
855 .get("project_domain_name"),
857 return self
._format
_out
(domains
)
858 except NbiException
as e
:
859 cherrypy
.response
.status
= e
.http_code
.value
861 "code": e
.http_code
.name
,
862 "status": e
.http_code
.value
,
865 return self
._format
_out
(problem_details
, None)
868 def _format_login(token_info
):
870 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
872 :param token_info: Dictionary with token content
875 cherrypy
.request
.login
= token_info
.get("username", "-")
876 if token_info
.get("project_name"):
877 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
878 if token_info
.get("id"):
879 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
881 # NS Fault Management
883 def nsfm(self
, version
=None, topic
=None, uuid
=None, project_name
=None, ns_id
=None, *args
, **kwargs
):
884 if topic
== 'alarms':
886 method
= cherrypy
.request
.method
887 role_permission
= self
._check
_valid
_url
_method
(method
, "nsfm", version
, topic
, None, None, *args
)
888 query_string_operations
= self
._extract
_query
_string
_operations
(kwargs
, method
)
890 self
.authenticator
.authorize(role_permission
, query_string_operations
, None)
892 # to handle get request
893 if cherrypy
.request
.method
== 'GET':
894 # if request is on basis of uuid
895 if uuid
and uuid
!= 'None':
897 alarm
= self
.engine
.db
.get_one("alarms", {"uuid": uuid
})
898 alarm_action
= self
.engine
.db
.get_one("alarms_action", {"uuid": uuid
})
899 alarm
.update(alarm_action
)
900 vnf
= self
.engine
.db
.get_one("vnfrs", {"nsr-id-ref": alarm
["tags"]["ns_id"]})
901 alarm
["vnf-id"] = vnf
["_id"]
902 return self
._format
_out
(str(alarm
))
904 return self
._format
_out
("Please provide valid alarm uuid")
905 elif ns_id
and ns_id
!= 'None':
906 # if request is on basis of ns_id
908 alarms
= self
.engine
.db
.get_list("alarms", {"tags.ns_id": ns_id
})
910 alarm_action
= self
.engine
.db
.get_one("alarms_action", {"uuid": alarm
['uuid']})
911 alarm
.update(alarm_action
)
912 return self
._format
_out
(str(alarms
))
914 return self
._format
_out
("Please provide valid ns id")
916 # to return only alarm which are related to given project
917 project
= self
.engine
.db
.get_one("projects", {"name": project_name
})
918 project_id
= project
.get('_id')
919 ns_list
= self
.engine
.db
.get_list("nsrs", {"_admin.projects_read": project_id
})
922 ns_ids
.append(ns
.get("_id"))
923 alarms
= self
.engine
.db
.get_list("alarms")
924 alarm_list
= [alarm
for alarm
in alarms
if alarm
["tags"]["ns_id"] in ns_ids
]
925 for alrm
in alarm_list
:
926 action
= self
.engine
.db
.get_one("alarms_action", {"uuid": alrm
.get("uuid")})
928 return self
._format
_out
(str(alarm_list
))
929 # to handle patch request for alarm update
930 elif cherrypy
.request
.method
== 'PATCH':
931 data
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
933 # check if uuid is valid
934 self
.engine
.db
.get_one("alarms", {"uuid": data
.get("uuid")})
936 return self
._format
_out
("Please provide valid alarm uuid.")
937 if data
.get("is_enable") is not None:
938 if data
.get("is_enable"):
941 alarm_status
= 'disabled'
942 self
.engine
.db
.set_one("alarms", {"uuid": data
.get("uuid")},
943 {"alarm_status": alarm_status
})
945 self
.engine
.db
.set_one("alarms", {"uuid": data
.get("uuid")},
946 {"threshold": data
.get("threshold")})
947 return self
._format
_out
("Alarm updated")
948 except Exception as e
:
949 cherrypy
.response
.status
= e
.http_code
.value
950 if isinstance(e
, (NbiException
, EngineException
, DbException
, FsException
, MsgException
, AuthException
,
951 ValidationError
, AuthconnException
)):
952 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
953 http_code_name
= e
.http_code
.name
954 cherrypy
.log("Exception {}".format(e
))
956 http_code_value
= cherrypy
.response
.status
= HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
957 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
958 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
960 "code": http_code_name
,
961 "status": http_code_value
,
964 return self
._format
_out
(problem_details
)
967 def token(self
, method
, token_id
=None, kwargs
=None):
969 # self.engine.load_dbase(cherrypy.request.app.config)
970 indata
= self
._format
_in
(kwargs
)
971 if not isinstance(indata
, dict):
973 "Expected application/yaml or application/json Content-Type",
974 HTTPStatus
.BAD_REQUEST
,
978 token_info
= self
.authenticator
.authorize()
980 self
._format
_login
(token_info
)
982 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
984 outdata
= self
.authenticator
.get_token_list(token_info
)
985 elif method
== "POST":
987 token_info
= self
.authenticator
.authorize()
991 indata
.update(kwargs
)
992 # This is needed to log the user when authentication fails
993 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
994 outdata
= token_info
= self
.authenticator
.new_token(
995 token_info
, indata
, cherrypy
.request
.remote
997 cherrypy
.session
["Authorization"] = outdata
["_id"]
998 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
1000 self
._format
_login
(token_info
)
1001 # password expiry check
1002 if self
.authenticator
.check_password_expiry(outdata
):
1003 outdata
= {"id": outdata
["id"],
1004 "message": "change_password",
1005 "user_id": outdata
["user_id"]
1007 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1008 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1009 elif method
== "DELETE":
1010 if not token_id
and "id" in kwargs
:
1011 token_id
= kwargs
["id"]
1013 token_info
= self
.authenticator
.authorize()
1015 self
._format
_login
(token_info
)
1016 token_id
= token_info
["_id"]
1017 outdata
= self
.authenticator
.del_token(token_id
)
1019 cherrypy
.session
["Authorization"] = "logout"
1020 # cherrypy.response.cookie["Authorization"] = token_id
1021 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1024 "Method {} not allowed for token".format(method
),
1025 HTTPStatus
.METHOD_NOT_ALLOWED
,
1027 return self
._format
_out
(outdata
, token_info
)
1030 def test(self
, *args
, **kwargs
):
1031 if not cherrypy
.config
.get("server.enable_test") or (
1032 isinstance(cherrypy
.config
["server.enable_test"], str)
1033 and cherrypy
.config
["server.enable_test"].lower() == "false"
1035 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
1036 return "test URL is disabled"
1038 if args
and args
[0] == "help":
1040 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1041 "sleep/<time>\nmessage/topic\n</pre></html>"
1044 elif args
and args
[0] == "init":
1046 # self.engine.load_dbase(cherrypy.request.app.config)
1047 self
.engine
.create_admin()
1048 return "Done. User 'admin', password 'admin' created"
1050 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1051 return self
._format
_out
("Database already initialized")
1052 elif args
and args
[0] == "file":
1053 return cherrypy
.lib
.static
.serve_file(
1054 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1],
1058 elif args
and args
[0] == "file2":
1060 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1]
1062 f
= open(f_path
, "r")
1063 cherrypy
.response
.headers
["Content-type"] = "text/plain"
1066 elif len(args
) == 2 and args
[0] == "db-clear":
1067 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
1068 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
1069 elif len(args
) and args
[0] == "fs-clear":
1071 folders
= (args
[1],)
1073 folders
= self
.engine
.fs
.dir_ls(".")
1074 for folder
in folders
:
1075 self
.engine
.fs
.file_delete(folder
)
1076 return ",".join(folders
) + " folders deleted\n"
1077 elif args
and args
[0] == "login":
1078 if not cherrypy
.request
.headers
.get("Authorization"):
1079 cherrypy
.response
.headers
[
1081 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
1082 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1083 elif args
and args
[0] == "login2":
1084 if not cherrypy
.request
.headers
.get("Authorization"):
1085 cherrypy
.response
.headers
[
1087 ] = 'Bearer realm="Access to OSM site"'
1088 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1089 elif args
and args
[0] == "sleep":
1092 sleep_time
= int(args
[1])
1094 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1095 return self
._format
_out
("Database already initialized")
1096 thread_info
= cherrypy
.thread_data
1098 time
.sleep(sleep_time
)
1100 elif len(args
) >= 2 and args
[0] == "message":
1101 main_topic
= args
[1]
1102 return_text
= "<html><pre>{} ->\n".format(main_topic
)
1104 if cherrypy
.request
.method
== "POST":
1105 to_send
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
1106 for k
, v
in to_send
.items():
1107 self
.engine
.msg
.write(main_topic
, k
, v
)
1108 return_text
+= " {}: {}\n".format(k
, v
)
1109 elif cherrypy
.request
.method
== "GET":
1110 for k
, v
in kwargs
.items():
1111 v_dict
= yaml
.load(v
, Loader
=yaml
.SafeLoader
)
1112 self
.engine
.msg
.write(main_topic
, k
, v_dict
)
1113 return_text
+= " {}: {}\n".format(k
, v_dict
)
1114 except Exception as e
:
1115 return_text
+= "Error: " + str(e
)
1116 return_text
+= "</pre></html>\n"
1120 "<html><pre>\nheaders:\n args: {}\n".format(args
)
1121 + " kwargs: {}\n".format(kwargs
)
1122 + " headers: {}\n".format(cherrypy
.request
.headers
)
1123 + " path_info: {}\n".format(cherrypy
.request
.path_info
)
1124 + " query_string: {}\n".format(cherrypy
.request
.query_string
)
1125 + " session: {}\n".format(cherrypy
.session
)
1126 + " cookie: {}\n".format(cherrypy
.request
.cookie
)
1127 + " method: {}\n".format(cherrypy
.request
.method
)
1128 + " session: {}\n".format(cherrypy
.session
.get("fieldname"))
1131 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
1132 if cherrypy
.request
.body
.length
:
1133 return_text
+= " content: {}\n".format(
1135 cherrypy
.request
.body
.read(
1136 int(cherrypy
.request
.headers
.get("Content-Length", 0))
1141 return_text
+= "thread: {}\n".format(thread_info
)
1142 return_text
+= "</pre></html>"
1146 def _check_valid_url_method(method
, *args
):
1149 "URL must contain at least 'main_topic/version/topic'",
1150 HTTPStatus
.METHOD_NOT_ALLOWED
,
1153 reference
= valid_url_methods
1157 if not isinstance(reference
, dict):
1159 "URL contains unexpected extra items '{}'".format(arg
),
1160 HTTPStatus
.METHOD_NOT_ALLOWED
,
1163 if arg
in reference
:
1164 reference
= reference
[arg
]
1165 elif "<ID>" in reference
:
1166 reference
= reference
["<ID>"]
1167 elif "*" in reference
:
1168 # if there is content
1170 reference
= reference
["*"]
1174 "Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
1176 if "TODO" in reference
and method
in reference
["TODO"]:
1178 "Method {} not supported yet for this URL".format(method
),
1179 HTTPStatus
.NOT_IMPLEMENTED
,
1181 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
1183 "Method {} not supported for this URL".format(method
),
1184 HTTPStatus
.METHOD_NOT_ALLOWED
,
1186 return reference
["ROLE_PERMISSION"] + method
.lower()
1189 def _set_location_header(main_topic
, version
, topic
, id):
1191 Insert response header Location with the URL of created item base on URL params
1198 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1199 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(
1200 main_topic
, version
, topic
, id
1205 def _extract_query_string_operations(kwargs
, method
):
1211 query_string_operations
= []
1213 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1214 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
1215 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
1216 return query_string_operations
1219 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
1221 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1222 Check that users has rights to use them and returs the admin_query
1223 :param token_info: token_info rights obtained by token
1224 :param kwargs: query string input.
1225 :param method: http method: GET, POSST, PUT, ...
1227 :return: admin_query dictionary with keys:
1228 public: True, False or None
1229 force: True or False
1230 project_id: tuple with projects used for accessing an element
1231 set_project: tuple with projects that a created element will belong to
1232 method: show, list, delete, write
1236 "project_id": (token_info
["project_id"],),
1237 "username": token_info
["username"],
1238 "admin": token_info
["admin"],
1240 "allow_show_user_project_role": token_info
["allow_show_user_project_role"],
1244 if "FORCE" in kwargs
:
1246 kwargs
["FORCE"].lower() != "false"
1247 ): # if None or True set force to True
1248 admin_query
["force"] = True
1251 if "PUBLIC" in kwargs
:
1253 kwargs
["PUBLIC"].lower() != "false"
1254 ): # if None or True set public to True
1255 admin_query
["public"] = True
1257 admin_query
["public"] = False
1258 del kwargs
["PUBLIC"]
1260 if "ADMIN" in kwargs
:
1261 behave_as
= kwargs
.pop("ADMIN")
1262 if behave_as
.lower() != "false":
1263 if not token_info
["admin"]:
1265 "Only admin projects can use 'ADMIN' query string",
1266 HTTPStatus
.UNAUTHORIZED
,
1269 not behave_as
or behave_as
.lower() == "true"
1270 ): # convert True, None to empty list
1271 admin_query
["project_id"] = ()
1272 elif isinstance(behave_as
, (list, tuple)):
1273 admin_query
["project_id"] = behave_as
1274 else: # isinstance(behave_as, str)
1275 admin_query
["project_id"] = (behave_as
,)
1276 if "SET_PROJECT" in kwargs
:
1277 set_project
= kwargs
.pop("SET_PROJECT")
1279 admin_query
["set_project"] = list(admin_query
["project_id"])
1281 if isinstance(set_project
, str):
1282 set_project
= (set_project
,)
1283 if admin_query
["project_id"]:
1284 for p
in set_project
:
1285 if p
not in admin_query
["project_id"]:
1287 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1288 "'ADMIN='{p}'".format(p
=p
),
1289 HTTPStatus
.UNAUTHORIZED
,
1291 admin_query
["set_project"] = set_project
1294 # if "PROJECT_READ" in kwargs:
1295 # admin_query["project"] = kwargs.pop("project")
1296 # if admin_query["project"] == token_info["project_id"]:
1299 admin_query
["method"] = "show"
1301 admin_query
["method"] = "list"
1302 elif method
== "DELETE":
1303 admin_query
["method"] = "delete"
1305 admin_query
["method"] = "write"
1325 engine_session
= None
1327 if not main_topic
or not version
or not topic
:
1329 "URL must contain at least 'main_topic/version/topic'",
1330 HTTPStatus
.METHOD_NOT_ALLOWED
,
1332 if main_topic
not in (
1344 "URL main_topic '{}' not supported".format(main_topic
),
1345 HTTPStatus
.METHOD_NOT_ALLOWED
,
1349 "URL version '{}' not supported".format(version
),
1350 HTTPStatus
.METHOD_NOT_ALLOWED
,
1355 and "METHOD" in kwargs
1356 and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1358 method
= kwargs
.pop("METHOD")
1360 method
= cherrypy
.request
.method
1362 role_permission
= self
._check
_valid
_url
_method
(
1363 method
, main_topic
, version
, topic
, _id
, item
, *args
1365 query_string_operations
= self
._extract
_query
_string
_operations
(
1368 if main_topic
== "admin" and topic
== "tokens":
1369 return self
.token(method
, _id
, kwargs
)
1370 token_info
= self
.authenticator
.authorize(
1371 role_permission
, query_string_operations
, _id
1373 if main_topic
== "admin" and topic
== "domains":
1374 return self
.domain()
1375 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
1376 indata
= self
._format
_in
(kwargs
)
1377 engine_topic
= topic
1379 if item
and topic
!= "pm_jobs":
1382 if main_topic
== "nsd":
1383 engine_topic
= "nsds"
1384 elif main_topic
== "vnfpkgm":
1385 engine_topic
= "vnfds"
1386 if topic
== "vnfpkg_op_occs":
1387 engine_topic
= "vnfpkgops"
1388 if topic
== "vnf_packages" and item
== "action":
1389 engine_topic
= "vnfpkgops"
1390 elif main_topic
== "nslcm":
1391 engine_topic
= "nsrs"
1392 if topic
== "ns_lcm_op_occs":
1393 engine_topic
= "nslcmops"
1394 if topic
== "vnfrs" or topic
== "vnf_instances":
1395 engine_topic
= "vnfrs"
1396 elif main_topic
== "vnflcm":
1397 if topic
== "vnf_lcm_op_occs":
1398 engine_topic
= "vnflcmops"
1399 elif main_topic
== "nst":
1400 engine_topic
= "nsts"
1401 elif main_topic
== "nsilcm":
1402 engine_topic
= "nsis"
1403 if topic
== "nsi_lcm_op_occs":
1404 engine_topic
= "nsilcmops"
1405 elif main_topic
== "pdu":
1406 engine_topic
= "pdus"
1408 engine_topic
== "vims"
1409 ): # TODO this is for backward compatibility, it will be removed in the future
1410 engine_topic
= "vim_accounts"
1412 if topic
== "subscriptions":
1413 engine_topic
= main_topic
+ "_" + topic
1425 if item
in ("vnfd", "nsd", "nst"):
1426 path
= "$DESCRIPTOR"
1429 elif item
== "artifacts":
1433 file, _format
= self
.engine
.get_file(
1438 cherrypy
.request
.headers
.get("Accept"),
1442 outdata
= self
.engine
.get_item_list(
1443 engine_session
, engine_topic
, kwargs
, api_req
=True
1446 if item
== "reports":
1447 # TODO check that project_id (_id in this context) has permissions
1450 if "vcaStatusRefresh" in kwargs
:
1451 filter_q
= {"vcaStatusRefresh": kwargs
["vcaStatusRefresh"]}
1452 outdata
= self
.engine
.get_item(engine_session
, engine_topic
, _id
, filter_q
, True)
1454 elif method
== "POST":
1455 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1457 "ns_descriptors_content",
1458 "vnf_packages_content",
1459 "netslice_templates_content",
1461 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1463 _id
, _
= self
.engine
.new_item(
1469 cherrypy
.request
.headers
,
1471 completed
= self
.engine
.upload_content(
1477 cherrypy
.request
.headers
,
1480 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1482 cherrypy
.response
.headers
["Transaction-Id"] = _id
1483 outdata
= {"id": _id
}
1484 elif topic
== "ns_instances_content":
1486 _id
, _
= self
.engine
.new_item(
1487 rollback
, engine_session
, engine_topic
, indata
, kwargs
1490 indata
["lcmOperationType"] = "instantiate"
1491 indata
["nsInstanceId"] = _id
1492 nslcmop_id
, _
= self
.engine
.new_item(
1493 rollback
, engine_session
, "nslcmops", indata
, None
1495 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1496 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1497 elif topic
== "ns_instances" and item
:
1498 indata
["lcmOperationType"] = item
1499 indata
["nsInstanceId"] = _id
1500 _id
, _
= self
.engine
.new_item(
1501 rollback
, engine_session
, "nslcmops", indata
, kwargs
1503 self
._set
_location
_header
(
1504 main_topic
, version
, "ns_lcm_op_occs", _id
1506 outdata
= {"id": _id
}
1507 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1508 elif topic
== "netslice_instances_content":
1509 # creates NetSlice_Instance_record (NSIR)
1510 _id
, _
= self
.engine
.new_item(
1511 rollback
, engine_session
, engine_topic
, indata
, kwargs
1513 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1514 indata
["lcmOperationType"] = "instantiate"
1515 indata
["netsliceInstanceId"] = _id
1516 nsilcmop_id
, _
= self
.engine
.new_item(
1517 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1519 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1520 elif topic
== "netslice_instances" and item
:
1521 indata
["lcmOperationType"] = item
1522 indata
["netsliceInstanceId"] = _id
1523 _id
, _
= self
.engine
.new_item(
1524 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1526 self
._set
_location
_header
(
1527 main_topic
, version
, "nsi_lcm_op_occs", _id
1529 outdata
= {"id": _id
}
1530 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1531 elif topic
== "vnf_packages" and item
== "action":
1532 indata
["lcmOperationType"] = item
1533 indata
["vnfPkgId"] = _id
1534 _id
, _
= self
.engine
.new_item(
1535 rollback
, engine_session
, "vnfpkgops", indata
, kwargs
1537 self
._set
_location
_header
(
1538 main_topic
, version
, "vnfpkg_op_occs", _id
1540 outdata
= {"id": _id
}
1541 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1542 elif topic
== "subscriptions":
1543 _id
, _
= self
.engine
.new_item(
1544 rollback
, engine_session
, engine_topic
, indata
, kwargs
1546 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1548 link
["self"] = cherrypy
.response
.headers
["Location"]
1551 "filter": indata
["filter"],
1552 "callbackUri": indata
["CallbackUri"],
1555 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1556 elif topic
== "vnf_instances" and item
:
1557 indata
["lcmOperationType"] = item
1558 indata
["vnfInstanceId"] = _id
1559 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, "vnflcmops", indata
, kwargs
)
1560 self
._set
_location
_header
(main_topic
, version
, "vnf_lcm_op_occs", _id
)
1561 outdata
= {"id": _id
}
1562 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1564 _id
, op_id
= self
.engine
.new_item(
1570 cherrypy
.request
.headers
,
1572 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1573 outdata
= {"id": _id
}
1575 outdata
["op_id"] = op_id
1576 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1577 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1579 elif method
== "DELETE":
1581 outdata
= self
.engine
.del_item_list(
1582 engine_session
, engine_topic
, kwargs
1584 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1585 else: # len(args) > 1
1586 # for NS NSI generate an operation
1588 if topic
== "ns_instances_content" and not engine_session
["force"]:
1590 "lcmOperationType": "terminate",
1591 "nsInstanceId": _id
,
1594 op_id
, _
= self
.engine
.new_item(
1595 rollback
, engine_session
, "nslcmops", nslcmop_desc
, kwargs
1598 outdata
= {"_id": op_id
}
1600 topic
== "netslice_instances_content"
1601 and not engine_session
["force"]
1604 "lcmOperationType": "terminate",
1605 "netsliceInstanceId": _id
,
1608 op_id
, _
= self
.engine
.new_item(
1609 rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None
1612 outdata
= {"_id": op_id
}
1613 # if there is not any deletion in process, delete
1615 op_id
= self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1617 outdata
= {"op_id": op_id
}
1618 cherrypy
.response
.status
= (
1619 HTTPStatus
.ACCEPTED
.value
1621 else HTTPStatus
.NO_CONTENT
.value
1624 elif method
in ("PUT", "PATCH"):
1626 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1628 "Nothing to update. Provide payload and/or query string",
1629 HTTPStatus
.BAD_REQUEST
,
1632 item
in ("nsd_content", "package_content", "nst_content")
1635 completed
= self
.engine
.upload_content(
1641 cherrypy
.request
.headers
,
1644 cherrypy
.response
.headers
["Transaction-Id"] = id
1646 op_id
= self
.engine
.edit_item(
1647 engine_session
, engine_topic
, _id
, indata
, kwargs
1651 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1652 outdata
= {"op_id": op_id
}
1654 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1658 "Method {} not allowed".format(method
),
1659 HTTPStatus
.METHOD_NOT_ALLOWED
,
1662 # if Role information changes, it is needed to reload the information of roles
1663 if topic
== "roles" and method
!= "GET":
1664 self
.authenticator
.load_operation_to_allowed_roles()
1668 and method
== "DELETE"
1669 or topic
in ["users", "roles"]
1670 and method
in ["PUT", "PATCH", "DELETE"]
1672 self
.authenticator
.remove_token_from_cache()
1674 return self
._format
_out
(outdata
, token_info
, _format
)
1675 except Exception as e
:
1689 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1690 http_code_name
= e
.http_code
.name
1691 cherrypy
.log("Exception {}".format(e
))
1694 cherrypy
.response
.status
1695 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1696 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1697 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1698 if hasattr(outdata
, "close"): # is an open file
1702 for rollback_item
in rollback
:
1704 if rollback_item
.get("operation") == "set":
1705 self
.engine
.db
.set_one(
1706 rollback_item
["topic"],
1707 {"_id": rollback_item
["_id"]},
1708 rollback_item
["content"],
1709 fail_on_empty
=False,
1711 elif rollback_item
.get("operation") == "del_list":
1712 self
.engine
.db
.del_list(
1713 rollback_item
["topic"],
1714 rollback_item
["filter"],
1715 fail_on_empty
=False,
1718 self
.engine
.db
.del_one(
1719 rollback_item
["topic"],
1720 {"_id": rollback_item
["_id"]},
1721 fail_on_empty
=False,
1723 except Exception as e2
:
1724 rollback_error_text
= "Rollback Exception {}: {}".format(
1727 cherrypy
.log(rollback_error_text
)
1728 error_text
+= ". " + rollback_error_text
1729 # if isinstance(e, MsgException):
1730 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1731 # engine_topic[:-1], method, error_text)
1733 "code": http_code_name
,
1734 "status": http_code_value
,
1735 "detail": error_text
,
1737 return self
._format
_out
(problem_details
, token_info
)
1738 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1741 self
._format
_login
(token_info
)
1742 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1743 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1744 if outdata
.get(logging_id
):
1745 cherrypy
.request
.login
+= ";{}={}".format(
1746 logging_id
, outdata
[logging_id
][:36]
1750 def _start_service():
1752 Callback function called when cherrypy.engine starts
1753 Override configuration with env variables
1754 Set database, storage, message configuration
1755 Init database with admin/admin user password
1758 global subscription_thread
1759 cherrypy
.log
.error("Starting osm_nbi")
1760 # update general cherrypy configuration
1763 engine_config
= cherrypy
.tree
.apps
["/osm"].config
1764 for k
, v
in environ
.items():
1765 if not k
.startswith("OSMNBI_"):
1767 k1
, _
, k2
= k
[7:].lower().partition("_")
1771 # update static configuration
1772 if k
== "OSMNBI_STATIC_DIR":
1773 engine_config
["/static"]["tools.staticdir.dir"] = v
1774 engine_config
["/static"]["tools.staticdir.on"] = True
1775 elif k
== "OSMNBI_SOCKET_PORT" or k
== "OSMNBI_SERVER_PORT":
1776 update_dict
["server.socket_port"] = int(v
)
1777 elif k
== "OSMNBI_SOCKET_HOST" or k
== "OSMNBI_SERVER_HOST":
1778 update_dict
["server.socket_host"] = v
1779 elif k1
in ("server", "test", "auth", "log"):
1780 update_dict
[k1
+ "." + k2
] = v
1781 elif k1
in ("message", "database", "storage", "authentication"):
1782 # k2 = k2.replace('_', '.')
1783 if k2
in ("port", "db_port"):
1784 engine_config
[k1
][k2
] = int(v
)
1786 engine_config
[k1
][k2
] = v
1788 except ValueError as e
:
1789 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1790 except Exception as e
:
1791 cherrypy
.log
.warn("skipping environ '{}' on exception '{}'".format(k
, e
))
1794 cherrypy
.config
.update(update_dict
)
1795 engine_config
["global"].update(update_dict
)
1798 log_format_simple
= (
1799 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1801 log_formatter_simple
= logging
.Formatter(
1802 log_format_simple
, datefmt
="%Y-%m-%dT%H:%M:%S"
1804 logger_server
= logging
.getLogger("cherrypy.error")
1805 logger_access
= logging
.getLogger("cherrypy.access")
1806 logger_cherry
= logging
.getLogger("cherrypy")
1807 logger_nbi
= logging
.getLogger("nbi")
1809 if "log.file" in engine_config
["global"]:
1810 file_handler
= logging
.handlers
.RotatingFileHandler(
1811 engine_config
["global"]["log.file"], maxBytes
=100e6
, backupCount
=9, delay
=0
1813 file_handler
.setFormatter(log_formatter_simple
)
1814 logger_cherry
.addHandler(file_handler
)
1815 logger_nbi
.addHandler(file_handler
)
1816 # log always to standard output
1817 for format_
, logger
in {
1818 "nbi.server %(filename)s:%(lineno)s": logger_server
,
1819 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1820 "%(name)s %(filename)s:%(lineno)s": logger_nbi
,
1822 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1823 log_formatter_cherry
= logging
.Formatter(
1824 log_format_cherry
, datefmt
="%Y-%m-%dT%H:%M:%S"
1826 str_handler
= logging
.StreamHandler()
1827 str_handler
.setFormatter(log_formatter_cherry
)
1828 logger
.addHandler(str_handler
)
1830 if engine_config
["global"].get("log.level"):
1831 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1832 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1834 # logging other modules
1835 for k1
, logname
in {
1836 "message": "nbi.msg",
1837 "database": "nbi.db",
1838 "storage": "nbi.fs",
1840 engine_config
[k1
]["logger_name"] = logname
1841 logger_module
= logging
.getLogger(logname
)
1842 if "logfile" in engine_config
[k1
]:
1843 file_handler
= logging
.handlers
.RotatingFileHandler(
1844 engine_config
[k1
]["logfile"], maxBytes
=100e6
, backupCount
=9, delay
=0
1846 file_handler
.setFormatter(log_formatter_simple
)
1847 logger_module
.addHandler(file_handler
)
1848 if "loglevel" in engine_config
[k1
]:
1849 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1850 # TODO add more entries, e.g.: storage
1851 cherrypy
.tree
.apps
["/osm"].root
.engine
.start(engine_config
)
1852 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.start(engine_config
)
1853 cherrypy
.tree
.apps
["/osm"].root
.engine
.init_db(target_version
=database_version
)
1854 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.init_db(
1855 target_version
=auth_database_version
1858 # start subscriptions thread:
1859 subscription_thread
= SubscriptionThread(
1860 config
=engine_config
, engine
=nbi_server
.engine
1862 subscription_thread
.start()
1863 # Do not capture except SubscriptionException
1865 backend
= engine_config
["authentication"]["backend"]
1867 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1868 nbi_version
, nbi_version_date
, backend
1873 def _stop_service():
1875 Callback function called when cherrypy.engine stops
1876 TODO: Ending database connections.
1878 global subscription_thread
1879 if subscription_thread
:
1880 subscription_thread
.terminate()
1881 subscription_thread
= None
1882 cherrypy
.tree
.apps
["/osm"].root
.engine
.stop()
1883 cherrypy
.log
.error("Stopping osm_nbi")
1886 def nbi(config_file
):
1890 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1891 # 'tools.sessions.on': True,
1892 # 'tools.response_headers.on': True,
1893 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1896 # cherrypy.Server.ssl_module = 'builtin'
1897 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1898 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1899 # cherrypy.Server.thread_pool = 10
1900 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1902 # cherrypy.config.update({'tools.auth_basic.on': True,
1903 # 'tools.auth_basic.realm': 'localhost',
1904 # 'tools.auth_basic.checkpassword': validate_password})
1905 nbi_server
= Server()
1906 cherrypy
.engine
.subscribe("start", _start_service
)
1907 cherrypy
.engine
.subscribe("stop", _stop_service
)
1908 cherrypy
.quickstart(nbi_server
, "/osm", config_file
)
1913 """Usage: {} [options]
1914 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1915 -h|--help: shows this help
1920 # --log-socket-host HOST: send logs to this host")
1921 # --log-socket-port PORT: send logs using this port (default: 9022)")
1924 if __name__
== "__main__":
1926 # load parameters and configuration
1927 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
1928 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1931 if o
in ("-h", "--help"):
1934 elif o
in ("-c", "--config"):
1936 # elif o == "--log-socket-port":
1937 # log_socket_port = a
1938 # elif o == "--log-socket-host":
1939 # log_socket_host = a
1940 # elif o == "--log-file":
1943 assert False, "Unhandled option"
1945 if not path
.isfile(config_file
):
1947 "configuration file '{}' that not exist".format(config_file
),
1952 for config_file
in (
1953 __file__
[: __file__
.rfind(".")] + ".cfg",
1957 if path
.isfile(config_file
):
1961 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
1966 except getopt
.GetoptError
as e
:
1967 print(str(e
), file=sys
.stderr
)