fa414f053384983fa4b1c786697f9f1bd27c8846
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:",
465 "METHODS": ("POST",),
466 "ROLE_PERMISSION": "ns_instances:id:verticalscale:",
472 "ROLE_PERMISSION": "ns_instances:opps:",
475 "ROLE_PERMISSION": "ns_instances:opps:id:",
480 "ROLE_PERMISSION": "vnf_instances:",
481 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
485 "ROLE_PERMISSION": "vnf_instances:",
486 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
489 "METHODS": ("GET", "POST"),
490 "ROLE_PERMISSION": "ns_subscriptions:",
492 "METHODS": ("GET", "DELETE"),
493 "ROLE_PERMISSION": "ns_subscriptions:id:",
501 "METHODS": ("GET", "POST"),
502 "ROLE_PERMISSION": "vnflcm_instances:",
504 "METHODS": ("GET", "DELETE"),
505 "ROLE_PERMISSION": "vnflcm_instances:id:",
507 "METHODS": ("POST",),
508 "ROLE_PERMISSION": "vnflcm_instances:id:scale:",
511 "METHODS": ("POST",),
512 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:",
515 "METHODS": ("POST",),
516 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:",
522 "ROLE_PERMISSION": "vnf_instances:opps:",
525 "ROLE_PERMISSION": "vnf_instances:opps:id:",
529 "METHODS": ("GET", "POST"),
530 "ROLE_PERMISSION": "vnflcm_subscriptions:",
532 "METHODS": ("GET", "DELETE"),
533 "ROLE_PERMISSION": "vnflcm_subscriptions:id:",
540 "netslice_templates_content": {
541 "METHODS": ("GET", "POST"),
542 "ROLE_PERMISSION": "slice_templates:",
544 "METHODS": ("GET", "PUT", "DELETE"),
545 "ROLE_PERMISSION": "slice_templates:id:",
548 "netslice_templates": {
549 "METHODS": ("GET", "POST"),
550 "ROLE_PERMISSION": "slice_templates:",
552 "METHODS": ("GET", "DELETE"),
554 "ROLE_PERMISSION": "slice_templates:id:",
556 "METHODS": ("GET", "PUT"),
557 "ROLE_PERMISSION": "slice_templates:id:content:",
560 "METHODS": ("GET",), # descriptor inside package
561 "ROLE_PERMISSION": "slice_templates:id:content:",
565 "ROLE_PERMISSION": "slice_templates:id:content:",
571 "TODO": ("GET", "POST"),
572 "<ID>": {"TODO": ("GET", "DELETE")},
578 "netslice_instances_content": {
579 "METHODS": ("GET", "POST"),
580 "ROLE_PERMISSION": "slice_instances:",
582 "METHODS": ("GET", "DELETE"),
583 "ROLE_PERMISSION": "slice_instances:id:",
586 "netslice_instances": {
587 "METHODS": ("GET", "POST"),
588 "ROLE_PERMISSION": "slice_instances:",
590 "METHODS": ("GET", "DELETE"),
591 "ROLE_PERMISSION": "slice_instances:id:",
593 "METHODS": ("POST",),
594 "ROLE_PERMISSION": "slice_instances:id:terminate:",
597 "METHODS": ("POST",),
598 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
601 "METHODS": ("POST",),
602 "ROLE_PERMISSION": "slice_instances:id:action:",
608 "ROLE_PERMISSION": "slice_instances:opps:",
611 "ROLE_PERMISSION": "slice_instances:opps:id:",
623 "ROLE_PERMISSION": "reports:id:",
633 "METHODS": ("GET", "PATCH"),
634 "ROLE_PERMISSION": "alarms:",
636 "METHODS": ("GET", "PATCH"),
637 "ROLE_PERMISSION": "alarms:id:",
645 class NbiException(Exception):
646 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
647 Exception.__init
__(self
, message
)
648 self
.http_code
= http_code
651 class Server(object):
653 # to decode bytes to str
654 reader
= getreader("utf-8")
658 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
659 self
.engine
= Engine(self
.authenticator
)
661 def _format_in(self
, kwargs
):
662 error_text
= "" # error_text must be initialized outside try
665 if cherrypy
.request
.body
.length
:
666 error_text
= "Invalid input format "
668 if "Content-Type" in cherrypy
.request
.headers
:
669 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
670 error_text
= "Invalid json format "
671 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
672 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
673 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
674 error_text
= "Invalid yaml format "
676 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
678 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
680 "application/binary" in cherrypy
.request
.headers
["Content-Type"]
681 or "application/gzip"
682 in cherrypy
.request
.headers
["Content-Type"]
683 or "application/zip" in cherrypy
.request
.headers
["Content-Type"]
684 or "text/plain" in cherrypy
.request
.headers
["Content-Type"]
686 indata
= cherrypy
.request
.body
# .read()
688 "multipart/form-data"
689 in cherrypy
.request
.headers
["Content-Type"]
691 if "descriptor_file" in kwargs
:
692 filecontent
= kwargs
.pop("descriptor_file")
693 if not filecontent
.file:
695 "empty file or content", HTTPStatus
.BAD_REQUEST
697 indata
= filecontent
.file # .read()
698 if filecontent
.content_type
.value
:
699 cherrypy
.request
.headers
[
701 ] = filecontent
.content_type
.value
703 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
704 # "Only 'Content-Type' of type 'application/json' or
705 # 'application/yaml' for input format are available")
706 error_text
= "Invalid yaml format "
708 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
710 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
712 error_text
= "Invalid yaml format "
713 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
714 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
719 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
722 for k
, v
in kwargs
.items():
723 if isinstance(v
, str):
728 kwargs
[k
] = yaml
.load(v
, Loader
=yaml
.SafeLoader
)
734 or k
.endswith(".gte")
735 or k
.endswith(".lte")
744 elif v
.find(",") > 0:
745 kwargs
[k
] = v
.split(",")
746 elif isinstance(v
, (list, tuple)):
747 for index
in range(0, len(v
)):
752 v
[index
] = yaml
.load(v
[index
], Loader
=yaml
.SafeLoader
)
757 except (ValueError, yaml
.YAMLError
) as exc
:
758 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
759 except KeyError as exc
:
761 "Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
763 except Exception as exc
:
764 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
767 def _format_out(data
, token_info
=None, _format
=None):
769 return string of dictionary data according to requested json, yaml, xml. By default json
770 :param data: response to be sent. Can be a dict, text or file
771 :param token_info: Contains among other username and project
772 :param _format: The format to be set as Content-Type if data is a file
775 accept
= cherrypy
.request
.headers
.get("Accept")
777 if accept
and "text/html" in accept
:
779 data
, cherrypy
.request
, cherrypy
.response
, token_info
781 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
783 elif hasattr(data
, "read"): # file object
785 cherrypy
.response
.headers
["Content-Type"] = _format
786 elif "b" in data
.mode
: # binariy asssumig zip
787 cherrypy
.response
.headers
["Content-Type"] = "application/zip"
789 cherrypy
.response
.headers
["Content-Type"] = "text/plain"
790 # TODO check that cherrypy close file. If not implement pending things to close per thread next
793 if "text/html" in accept
:
795 data
, cherrypy
.request
, cherrypy
.response
, token_info
797 elif "application/yaml" in accept
or "*/*" in accept
:
799 elif "application/json" in accept
or (
800 cherrypy
.response
.status
and cherrypy
.response
.status
>= 300
802 cherrypy
.response
.headers
[
804 ] = "application/json; charset=utf-8"
805 a
= json
.dumps(data
, indent
=4) + "\n"
806 return a
.encode("utf8")
807 cherrypy
.response
.headers
["Content-Type"] = "application/yaml"
808 return yaml
.safe_dump(
812 default_flow_style
=False,
816 ) # , canonical=True, default_style='"'
819 def index(self
, *args
, **kwargs
):
822 if cherrypy
.request
.method
== "GET":
823 token_info
= self
.authenticator
.authorize()
824 outdata
= token_info
# Home page
826 raise cherrypy
.HTTPError(
827 HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
828 "Method {} not allowed for tokens".format(cherrypy
.request
.method
),
831 return self
._format
_out
(outdata
, token_info
)
833 except (EngineException
, AuthException
) as e
:
834 # cherrypy.log("index Exception {}".format(e))
835 cherrypy
.response
.status
= e
.http_code
.value
836 return self
._format
_out
("Welcome to OSM!", token_info
)
839 def version(self
, *args
, **kwargs
):
840 # TODO consider to remove and provide version using the static version file
842 if cherrypy
.request
.method
!= "GET":
844 "Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
848 "Invalid URL or query string for version",
849 HTTPStatus
.METHOD_NOT_ALLOWED
,
851 # TODO include version of other modules, pick up from some kafka admin message
852 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
853 return self
._format
_out
(osm_nbi_version
)
854 except NbiException
as e
:
855 cherrypy
.response
.status
= e
.http_code
.value
857 "code": e
.http_code
.name
,
858 "status": e
.http_code
.value
,
861 return self
._format
_out
(problem_details
, None)
866 "user_domain_name": cherrypy
.tree
.apps
["/osm"]
867 .config
["authentication"]
868 .get("user_domain_name"),
869 "project_domain_name": cherrypy
.tree
.apps
["/osm"]
870 .config
["authentication"]
871 .get("project_domain_name"),
873 return self
._format
_out
(domains
)
874 except NbiException
as e
:
875 cherrypy
.response
.status
= e
.http_code
.value
877 "code": e
.http_code
.name
,
878 "status": e
.http_code
.value
,
881 return self
._format
_out
(problem_details
, None)
884 def _format_login(token_info
):
886 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
888 :param token_info: Dictionary with token content
891 cherrypy
.request
.login
= token_info
.get("username", "-")
892 if token_info
.get("project_name"):
893 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
894 if token_info
.get("id"):
895 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
897 # NS Fault Management
909 if topic
== "alarms":
911 method
= cherrypy
.request
.method
912 role_permission
= self
._check
_valid
_url
_method
(
913 method
, "nsfm", version
, topic
, None, None, *args
915 query_string_operations
= self
._extract
_query
_string
_operations
(
919 self
.authenticator
.authorize(
920 role_permission
, query_string_operations
, None
923 # to handle get request
924 if cherrypy
.request
.method
== "GET":
925 # if request is on basis of uuid
926 if uuid
and uuid
!= "None":
928 alarm
= self
.engine
.db
.get_one("alarms", {"uuid": uuid
})
929 alarm_action
= self
.engine
.db
.get_one(
930 "alarms_action", {"uuid": uuid
}
932 alarm
.update(alarm_action
)
933 vnf
= self
.engine
.db
.get_one(
934 "vnfrs", {"nsr-id-ref": alarm
["tags"]["ns_id"]}
936 alarm
["vnf-id"] = vnf
["_id"]
937 return self
._format
_out
(str(alarm
))
939 return self
._format
_out
("Please provide valid alarm uuid")
940 elif ns_id
and ns_id
!= "None":
941 # if request is on basis of ns_id
943 alarms
= self
.engine
.db
.get_list(
944 "alarms", {"tags.ns_id": ns_id
}
947 alarm_action
= self
.engine
.db
.get_one(
948 "alarms_action", {"uuid": alarm
["uuid"]}
950 alarm
.update(alarm_action
)
951 return self
._format
_out
(str(alarms
))
953 return self
._format
_out
("Please provide valid ns id")
955 # to return only alarm which are related to given project
956 project
= self
.engine
.db
.get_one(
957 "projects", {"name": project_name
}
959 project_id
= project
.get("_id")
960 ns_list
= self
.engine
.db
.get_list(
961 "nsrs", {"_admin.projects_read": project_id
}
965 ns_ids
.append(ns
.get("_id"))
966 alarms
= self
.engine
.db
.get_list("alarms")
970 if alarm
["tags"]["ns_id"] in ns_ids
972 for alrm
in alarm_list
:
973 action
= self
.engine
.db
.get_one(
974 "alarms_action", {"uuid": alrm
.get("uuid")}
977 return self
._format
_out
(str(alarm_list
))
978 # to handle patch request for alarm update
979 elif cherrypy
.request
.method
== "PATCH":
980 data
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
982 # check if uuid is valid
983 self
.engine
.db
.get_one("alarms", {"uuid": data
.get("uuid")})
985 return self
._format
_out
("Please provide valid alarm uuid.")
986 if data
.get("is_enable") is not None:
987 if data
.get("is_enable"):
990 alarm_status
= "disabled"
991 self
.engine
.db
.set_one(
993 {"uuid": data
.get("uuid")},
994 {"alarm_status": alarm_status
},
997 self
.engine
.db
.set_one(
999 {"uuid": data
.get("uuid")},
1000 {"threshold": data
.get("threshold")},
1002 return self
._format
_out
("Alarm updated")
1003 except Exception as e
:
1017 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1018 http_code_name
= e
.http_code
.name
1019 cherrypy
.log("Exception {}".format(e
))
1022 cherrypy
.response
.status
1023 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1024 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1025 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1027 "code": http_code_name
,
1028 "status": http_code_value
,
1031 return self
._format
_out
(problem_details
)
1034 def token(self
, method
, token_id
=None, kwargs
=None):
1036 # self.engine.load_dbase(cherrypy.request.app.config)
1037 indata
= self
._format
_in
(kwargs
)
1038 if not isinstance(indata
, dict):
1040 "Expected application/yaml or application/json Content-Type",
1041 HTTPStatus
.BAD_REQUEST
,
1045 token_info
= self
.authenticator
.authorize()
1047 self
._format
_login
(token_info
)
1049 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
1051 outdata
= self
.authenticator
.get_token_list(token_info
)
1052 elif method
== "POST":
1054 token_info
= self
.authenticator
.authorize()
1058 indata
.update(kwargs
)
1059 # This is needed to log the user when authentication fails
1060 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
1061 outdata
= token_info
= self
.authenticator
.new_token(
1062 token_info
, indata
, cherrypy
.request
.remote
1064 cherrypy
.session
["Authorization"] = outdata
["_id"] # pylint: disable=E1101
1065 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
1067 self
._format
_login
(token_info
)
1068 # password expiry check
1069 if self
.authenticator
.check_password_expiry(outdata
):
1071 "id": outdata
["id"],
1072 "message": "change_password",
1073 "user_id": outdata
["user_id"],
1075 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1076 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1077 elif method
== "DELETE":
1078 if not token_id
and "id" in kwargs
:
1079 token_id
= kwargs
["id"]
1081 token_info
= self
.authenticator
.authorize()
1083 self
._format
_login
(token_info
)
1084 token_id
= token_info
["_id"]
1085 outdata
= self
.authenticator
.del_token(token_id
)
1087 cherrypy
.session
["Authorization"] = "logout" # pylint: disable=E1101
1088 # cherrypy.response.cookie["Authorization"] = token_id
1089 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1092 "Method {} not allowed for token".format(method
),
1093 HTTPStatus
.METHOD_NOT_ALLOWED
,
1095 return self
._format
_out
(outdata
, token_info
)
1098 def test(self
, *args
, **kwargs
):
1099 if not cherrypy
.config
.get("server.enable_test") or (
1100 isinstance(cherrypy
.config
["server.enable_test"], str)
1101 and cherrypy
.config
["server.enable_test"].lower() == "false"
1103 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
1104 return "test URL is disabled"
1106 if args
and args
[0] == "help":
1108 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1109 "sleep/<time>\nmessage/topic\n</pre></html>"
1112 elif args
and args
[0] == "init":
1114 # self.engine.load_dbase(cherrypy.request.app.config)
1115 pid
= self
.authenticator
.create_admin_project()
1116 self
.authenticator
.create_admin_user(pid
)
1117 return "Done. User 'admin', password 'admin' created"
1119 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1120 return self
._format
_out
("Database already initialized")
1121 elif args
and args
[0] == "file":
1122 return cherrypy
.lib
.static
.serve_file(
1123 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1],
1127 elif args
and args
[0] == "file2":
1129 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1]
1131 f
= open(f_path
, "r")
1132 cherrypy
.response
.headers
["Content-type"] = "text/plain"
1135 elif len(args
) == 2 and args
[0] == "db-clear":
1136 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
1137 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
1138 elif len(args
) and args
[0] == "fs-clear":
1140 folders
= (args
[1],)
1142 folders
= self
.engine
.fs
.dir_ls(".")
1143 for folder
in folders
:
1144 self
.engine
.fs
.file_delete(folder
)
1145 return ",".join(folders
) + " folders deleted\n"
1146 elif args
and args
[0] == "login":
1147 if not cherrypy
.request
.headers
.get("Authorization"):
1148 cherrypy
.response
.headers
[
1150 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
1151 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1152 elif args
and args
[0] == "login2":
1153 if not cherrypy
.request
.headers
.get("Authorization"):
1154 cherrypy
.response
.headers
[
1156 ] = 'Bearer realm="Access to OSM site"'
1157 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1158 elif args
and args
[0] == "sleep":
1161 sleep_time
= int(args
[1])
1163 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1164 return self
._format
_out
("Database already initialized")
1165 thread_info
= cherrypy
.thread_data
1167 time
.sleep(sleep_time
)
1169 elif len(args
) >= 2 and args
[0] == "message":
1170 main_topic
= args
[1]
1171 return_text
= "<html><pre>{} ->\n".format(main_topic
)
1173 if cherrypy
.request
.method
== "POST":
1174 to_send
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
1175 for k
, v
in to_send
.items():
1176 self
.engine
.msg
.write(main_topic
, k
, v
)
1177 return_text
+= " {}: {}\n".format(k
, v
)
1178 elif cherrypy
.request
.method
== "GET":
1179 for k
, v
in kwargs
.items():
1180 v_dict
= yaml
.load(v
, Loader
=yaml
.SafeLoader
)
1181 self
.engine
.msg
.write(main_topic
, k
, v_dict
)
1182 return_text
+= " {}: {}\n".format(k
, v_dict
)
1183 except Exception as e
:
1184 return_text
+= "Error: " + str(e
)
1185 return_text
+= "</pre></html>\n"
1189 "<html><pre>\nheaders:\n args: {}\n".format(args
)
1190 + " kwargs: {}\n".format(kwargs
)
1191 + " headers: {}\n".format(cherrypy
.request
.headers
)
1192 + " path_info: {}\n".format(cherrypy
.request
.path_info
)
1193 + " query_string: {}\n".format(cherrypy
.request
.query_string
)
1194 + " session: {}\n".format(cherrypy
.session
) # pylint: disable=E1101
1195 + " cookie: {}\n".format(cherrypy
.request
.cookie
)
1196 + " method: {}\n".format(cherrypy
.request
.method
)
1197 + " session: {}\n".format(
1198 cherrypy
.session
.get("fieldname") # pylint: disable=E1101
1202 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
1203 if cherrypy
.request
.body
.length
:
1204 return_text
+= " content: {}\n".format(
1206 cherrypy
.request
.body
.read(
1207 int(cherrypy
.request
.headers
.get("Content-Length", 0))
1212 return_text
+= "thread: {}\n".format(thread_info
)
1213 return_text
+= "</pre></html>"
1217 def _check_valid_url_method(method
, *args
):
1220 "URL must contain at least 'main_topic/version/topic'",
1221 HTTPStatus
.METHOD_NOT_ALLOWED
,
1224 reference
= valid_url_methods
1228 if not isinstance(reference
, dict):
1230 "URL contains unexpected extra items '{}'".format(arg
),
1231 HTTPStatus
.METHOD_NOT_ALLOWED
,
1234 if arg
in reference
:
1235 reference
= reference
[arg
]
1236 elif "<ID>" in reference
:
1237 reference
= reference
["<ID>"]
1238 elif "*" in reference
:
1239 # if there is content
1241 reference
= reference
["*"]
1245 "Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
1247 if "TODO" in reference
and method
in reference
["TODO"]:
1249 "Method {} not supported yet for this URL".format(method
),
1250 HTTPStatus
.NOT_IMPLEMENTED
,
1252 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
1254 "Method {} not supported for this URL".format(method
),
1255 HTTPStatus
.METHOD_NOT_ALLOWED
,
1257 return reference
["ROLE_PERMISSION"] + method
.lower()
1260 def _set_location_header(main_topic
, version
, topic
, id):
1262 Insert response header Location with the URL of created item base on URL params
1269 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1270 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(
1271 main_topic
, version
, topic
, id
1276 def _extract_query_string_operations(kwargs
, method
):
1282 query_string_operations
= []
1284 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1285 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
1286 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
1287 return query_string_operations
1290 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
1292 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1293 Check that users has rights to use them and returs the admin_query
1294 :param token_info: token_info rights obtained by token
1295 :param kwargs: query string input.
1296 :param method: http method: GET, POSST, PUT, ...
1298 :return: admin_query dictionary with keys:
1299 public: True, False or None
1300 force: True or False
1301 project_id: tuple with projects used for accessing an element
1302 set_project: tuple with projects that a created element will belong to
1303 method: show, list, delete, write
1307 "project_id": (token_info
["project_id"],),
1308 "username": token_info
["username"],
1309 "admin": token_info
["admin"],
1311 "allow_show_user_project_role": token_info
["allow_show_user_project_role"],
1315 if "FORCE" in kwargs
:
1317 kwargs
["FORCE"].lower() != "false"
1318 ): # if None or True set force to True
1319 admin_query
["force"] = True
1322 if "PUBLIC" in kwargs
:
1324 kwargs
["PUBLIC"].lower() != "false"
1325 ): # if None or True set public to True
1326 admin_query
["public"] = True
1328 admin_query
["public"] = False
1329 del kwargs
["PUBLIC"]
1331 if "ADMIN" in kwargs
:
1332 behave_as
= kwargs
.pop("ADMIN")
1333 if behave_as
.lower() != "false":
1334 if not token_info
["admin"]:
1336 "Only admin projects can use 'ADMIN' query string",
1337 HTTPStatus
.UNAUTHORIZED
,
1340 not behave_as
or behave_as
.lower() == "true"
1341 ): # convert True, None to empty list
1342 admin_query
["project_id"] = ()
1343 elif isinstance(behave_as
, (list, tuple)):
1344 admin_query
["project_id"] = behave_as
1345 else: # isinstance(behave_as, str)
1346 admin_query
["project_id"] = (behave_as
,)
1347 if "SET_PROJECT" in kwargs
:
1348 set_project
= kwargs
.pop("SET_PROJECT")
1350 admin_query
["set_project"] = list(admin_query
["project_id"])
1352 if isinstance(set_project
, str):
1353 set_project
= (set_project
,)
1354 if admin_query
["project_id"]:
1355 for p
in set_project
:
1356 if p
not in admin_query
["project_id"]:
1358 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1359 "'ADMIN='{p}'".format(p
=p
),
1360 HTTPStatus
.UNAUTHORIZED
,
1362 admin_query
["set_project"] = set_project
1365 # if "PROJECT_READ" in kwargs:
1366 # admin_query["project"] = kwargs.pop("project")
1367 # if admin_query["project"] == token_info["project_id"]:
1370 admin_query
["method"] = "show"
1372 admin_query
["method"] = "list"
1373 elif method
== "DELETE":
1374 admin_query
["method"] = "delete"
1376 admin_query
["method"] = "write"
1396 engine_session
= None
1398 if not main_topic
or not version
or not topic
:
1400 "URL must contain at least 'main_topic/version/topic'",
1401 HTTPStatus
.METHOD_NOT_ALLOWED
,
1403 if main_topic
not in (
1415 "URL main_topic '{}' not supported".format(main_topic
),
1416 HTTPStatus
.METHOD_NOT_ALLOWED
,
1420 "URL version '{}' not supported".format(version
),
1421 HTTPStatus
.METHOD_NOT_ALLOWED
,
1426 and "METHOD" in kwargs
1427 and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1429 method
= kwargs
.pop("METHOD")
1431 method
= cherrypy
.request
.method
1433 role_permission
= self
._check
_valid
_url
_method
(
1434 method
, main_topic
, version
, topic
, _id
, item
, *args
1436 query_string_operations
= self
._extract
_query
_string
_operations
(
1439 if main_topic
== "admin" and topic
== "tokens":
1440 return self
.token(method
, _id
, kwargs
)
1441 token_info
= self
.authenticator
.authorize(
1442 role_permission
, query_string_operations
, _id
1444 if main_topic
== "admin" and topic
== "domains":
1445 return self
.domain()
1446 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
1447 indata
= self
._format
_in
(kwargs
)
1448 engine_topic
= topic
1450 if item
and topic
!= "pm_jobs":
1453 if main_topic
== "nsd":
1454 engine_topic
= "nsds"
1455 elif main_topic
== "vnfpkgm":
1456 engine_topic
= "vnfds"
1457 if topic
== "vnfpkg_op_occs":
1458 engine_topic
= "vnfpkgops"
1459 if topic
== "vnf_packages" and item
== "action":
1460 engine_topic
= "vnfpkgops"
1461 elif main_topic
== "nslcm":
1462 engine_topic
= "nsrs"
1463 if topic
== "ns_lcm_op_occs":
1464 engine_topic
= "nslcmops"
1465 if topic
== "vnfrs" or topic
== "vnf_instances":
1466 engine_topic
= "vnfrs"
1467 elif main_topic
== "vnflcm":
1468 if topic
== "vnf_lcm_op_occs":
1469 engine_topic
= "vnflcmops"
1470 elif main_topic
== "nst":
1471 engine_topic
= "nsts"
1472 elif main_topic
== "nsilcm":
1473 engine_topic
= "nsis"
1474 if topic
== "nsi_lcm_op_occs":
1475 engine_topic
= "nsilcmops"
1476 elif main_topic
== "pdu":
1477 engine_topic
= "pdus"
1479 engine_topic
== "vims"
1480 ): # TODO this is for backward compatibility, it will be removed in the future
1481 engine_topic
= "vim_accounts"
1483 if topic
== "subscriptions":
1484 engine_topic
= main_topic
+ "_" + topic
1496 if item
in ("vnfd", "nsd", "nst"):
1497 path
= "$DESCRIPTOR"
1500 elif item
== "artifacts":
1504 file, _format
= self
.engine
.get_file(
1509 cherrypy
.request
.headers
.get("Accept"),
1513 outdata
= self
.engine
.get_item_list(
1514 engine_session
, engine_topic
, kwargs
, api_req
=True
1517 if item
== "reports":
1518 # TODO check that project_id (_id in this context) has permissions
1521 if "vcaStatusRefresh" in kwargs
:
1522 filter_q
= {"vcaStatusRefresh": kwargs
["vcaStatusRefresh"]}
1523 outdata
= self
.engine
.get_item(
1524 engine_session
, engine_topic
, _id
, filter_q
, True
1527 elif method
== "POST":
1528 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1530 "ns_descriptors_content",
1531 "vnf_packages_content",
1532 "netslice_templates_content",
1534 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1536 _id
, _
= self
.engine
.new_item(
1542 cherrypy
.request
.headers
,
1544 completed
= self
.engine
.upload_content(
1550 cherrypy
.request
.headers
,
1553 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1555 cherrypy
.response
.headers
["Transaction-Id"] = _id
1556 outdata
= {"id": _id
}
1557 elif topic
== "ns_instances_content":
1559 _id
, _
= self
.engine
.new_item(
1560 rollback
, engine_session
, engine_topic
, indata
, kwargs
1563 indata
["lcmOperationType"] = "instantiate"
1564 indata
["nsInstanceId"] = _id
1565 nslcmop_id
, _
= self
.engine
.new_item(
1566 rollback
, engine_session
, "nslcmops", indata
, None
1568 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1569 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1570 elif topic
== "ns_instances" and item
:
1571 indata
["lcmOperationType"] = item
1572 indata
["nsInstanceId"] = _id
1573 _id
, _
= self
.engine
.new_item(
1574 rollback
, engine_session
, "nslcmops", indata
, kwargs
1576 self
._set
_location
_header
(
1577 main_topic
, version
, "ns_lcm_op_occs", _id
1579 outdata
= {"id": _id
}
1580 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1581 elif topic
== "netslice_instances_content":
1582 # creates NetSlice_Instance_record (NSIR)
1583 _id
, _
= self
.engine
.new_item(
1584 rollback
, engine_session
, engine_topic
, indata
, kwargs
1586 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1587 indata
["lcmOperationType"] = "instantiate"
1588 indata
["netsliceInstanceId"] = _id
1589 nsilcmop_id
, _
= self
.engine
.new_item(
1590 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1592 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1593 elif topic
== "netslice_instances" and item
:
1594 indata
["lcmOperationType"] = item
1595 indata
["netsliceInstanceId"] = _id
1596 _id
, _
= self
.engine
.new_item(
1597 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1599 self
._set
_location
_header
(
1600 main_topic
, version
, "nsi_lcm_op_occs", _id
1602 outdata
= {"id": _id
}
1603 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1604 elif topic
== "vnf_packages" and item
== "action":
1605 indata
["lcmOperationType"] = item
1606 indata
["vnfPkgId"] = _id
1607 _id
, _
= self
.engine
.new_item(
1608 rollback
, engine_session
, "vnfpkgops", indata
, kwargs
1610 self
._set
_location
_header
(
1611 main_topic
, version
, "vnfpkg_op_occs", _id
1613 outdata
= {"id": _id
}
1614 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1615 elif topic
== "subscriptions":
1616 _id
, _
= self
.engine
.new_item(
1617 rollback
, engine_session
, engine_topic
, indata
, kwargs
1619 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1621 link
["self"] = cherrypy
.response
.headers
["Location"]
1624 "filter": indata
["filter"],
1625 "callbackUri": indata
["CallbackUri"],
1628 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1629 elif topic
== "vnf_instances" and item
:
1630 indata
["lcmOperationType"] = item
1631 indata
["vnfInstanceId"] = _id
1632 _id
, _
= self
.engine
.new_item(
1633 rollback
, engine_session
, "vnflcmops", indata
, kwargs
1635 self
._set
_location
_header
(
1636 main_topic
, version
, "vnf_lcm_op_occs", _id
1638 outdata
= {"id": _id
}
1639 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1641 _id
, op_id
= self
.engine
.new_item(
1647 cherrypy
.request
.headers
,
1649 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1650 outdata
= {"id": _id
}
1652 outdata
["op_id"] = op_id
1653 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1654 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1656 elif method
== "DELETE":
1658 outdata
= self
.engine
.del_item_list(
1659 engine_session
, engine_topic
, kwargs
1661 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1662 else: # len(args) > 1
1663 # for NS NSI generate an operation
1665 if topic
== "ns_instances_content" and not engine_session
["force"]:
1667 "lcmOperationType": "terminate",
1668 "nsInstanceId": _id
,
1671 op_id
, _
= self
.engine
.new_item(
1672 rollback
, engine_session
, "nslcmops", nslcmop_desc
, kwargs
1675 outdata
= {"_id": op_id
}
1677 topic
== "netslice_instances_content"
1678 and not engine_session
["force"]
1681 "lcmOperationType": "terminate",
1682 "netsliceInstanceId": _id
,
1685 op_id
, _
= self
.engine
.new_item(
1686 rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None
1689 outdata
= {"_id": op_id
}
1690 # if there is not any deletion in process, delete
1692 op_id
= self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1694 outdata
= {"op_id": op_id
}
1695 cherrypy
.response
.status
= (
1696 HTTPStatus
.ACCEPTED
.value
1698 else HTTPStatus
.NO_CONTENT
.value
1701 elif method
in ("PUT", "PATCH"):
1703 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1705 "Nothing to update. Provide payload and/or query string",
1706 HTTPStatus
.BAD_REQUEST
,
1709 item
in ("nsd_content", "package_content", "nst_content")
1712 completed
= self
.engine
.upload_content(
1718 cherrypy
.request
.headers
,
1721 cherrypy
.response
.headers
["Transaction-Id"] = id
1723 op_id
= self
.engine
.edit_item(
1724 engine_session
, engine_topic
, _id
, indata
, kwargs
1728 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1729 outdata
= {"op_id": op_id
}
1731 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1735 "Method {} not allowed".format(method
),
1736 HTTPStatus
.METHOD_NOT_ALLOWED
,
1739 # if Role information changes, it is needed to reload the information of roles
1740 if topic
== "roles" and method
!= "GET":
1741 self
.authenticator
.load_operation_to_allowed_roles()
1745 and method
== "DELETE"
1746 or topic
in ["users", "roles"]
1747 and method
in ["PUT", "PATCH", "DELETE"]
1749 self
.authenticator
.remove_token_from_cache()
1751 return self
._format
_out
(outdata
, token_info
, _format
)
1752 except Exception as e
:
1766 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1767 http_code_name
= e
.http_code
.name
1768 cherrypy
.log("Exception {}".format(e
))
1771 cherrypy
.response
.status
1772 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1773 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1774 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1775 if hasattr(outdata
, "close"): # is an open file
1779 for rollback_item
in rollback
:
1781 if rollback_item
.get("operation") == "set":
1782 self
.engine
.db
.set_one(
1783 rollback_item
["topic"],
1784 {"_id": rollback_item
["_id"]},
1785 rollback_item
["content"],
1786 fail_on_empty
=False,
1788 elif rollback_item
.get("operation") == "del_list":
1789 self
.engine
.db
.del_list(
1790 rollback_item
["topic"],
1791 rollback_item
["filter"],
1794 self
.engine
.db
.del_one(
1795 rollback_item
["topic"],
1796 {"_id": rollback_item
["_id"]},
1797 fail_on_empty
=False,
1799 except Exception as e2
:
1800 rollback_error_text
= "Rollback Exception {}: {}".format(
1803 cherrypy
.log(rollback_error_text
)
1804 error_text
+= ". " + rollback_error_text
1805 # if isinstance(e, MsgException):
1806 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1807 # engine_topic[:-1], method, error_text)
1809 "code": http_code_name
,
1810 "status": http_code_value
,
1811 "detail": error_text
,
1813 return self
._format
_out
(problem_details
, token_info
)
1814 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1817 self
._format
_login
(token_info
)
1818 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1819 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1820 if outdata
.get(logging_id
):
1821 cherrypy
.request
.login
+= ";{}={}".format(
1822 logging_id
, outdata
[logging_id
][:36]
1826 def _start_service():
1828 Callback function called when cherrypy.engine starts
1829 Override configuration with env variables
1830 Set database, storage, message configuration
1831 Init database with admin/admin user password
1834 global subscription_thread
1835 cherrypy
.log
.error("Starting osm_nbi")
1836 # update general cherrypy configuration
1839 engine_config
= cherrypy
.tree
.apps
["/osm"].config
1840 for k
, v
in environ
.items():
1841 if not k
.startswith("OSMNBI_"):
1843 k1
, _
, k2
= k
[7:].lower().partition("_")
1847 # update static configuration
1848 if k
== "OSMNBI_STATIC_DIR":
1849 engine_config
["/static"]["tools.staticdir.dir"] = v
1850 engine_config
["/static"]["tools.staticdir.on"] = True
1851 elif k
== "OSMNBI_SOCKET_PORT" or k
== "OSMNBI_SERVER_PORT":
1852 update_dict
["server.socket_port"] = int(v
)
1853 elif k
== "OSMNBI_SOCKET_HOST" or k
== "OSMNBI_SERVER_HOST":
1854 update_dict
["server.socket_host"] = v
1855 elif k1
in ("server", "test", "auth", "log"):
1856 update_dict
[k1
+ "." + k2
] = v
1857 elif k1
in ("message", "database", "storage", "authentication"):
1858 # k2 = k2.replace('_', '.')
1859 if k2
in ("port", "db_port"):
1860 engine_config
[k1
][k2
] = int(v
)
1862 engine_config
[k1
][k2
] = v
1864 except ValueError as e
:
1865 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1866 except Exception as e
:
1868 "WARNING: skipping environ '{}' on exception '{}'".format(k
, e
)
1872 cherrypy
.config
.update(update_dict
)
1873 engine_config
["global"].update(update_dict
)
1876 log_format_simple
= (
1877 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1879 log_formatter_simple
= logging
.Formatter(
1880 log_format_simple
, datefmt
="%Y-%m-%dT%H:%M:%S"
1882 logger_server
= logging
.getLogger("cherrypy.error")
1883 logger_access
= logging
.getLogger("cherrypy.access")
1884 logger_cherry
= logging
.getLogger("cherrypy")
1885 logger_nbi
= logging
.getLogger("nbi")
1887 if "log.file" in engine_config
["global"]:
1888 file_handler
= logging
.handlers
.RotatingFileHandler(
1889 engine_config
["global"]["log.file"], maxBytes
=100e6
, backupCount
=9, delay
=0
1891 file_handler
.setFormatter(log_formatter_simple
)
1892 logger_cherry
.addHandler(file_handler
)
1893 logger_nbi
.addHandler(file_handler
)
1894 # log always to standard output
1895 for format_
, logger
in {
1896 "nbi.server %(filename)s:%(lineno)s": logger_server
,
1897 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1898 "%(name)s %(filename)s:%(lineno)s": logger_nbi
,
1900 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1901 log_formatter_cherry
= logging
.Formatter(
1902 log_format_cherry
, datefmt
="%Y-%m-%dT%H:%M:%S"
1904 str_handler
= logging
.StreamHandler()
1905 str_handler
.setFormatter(log_formatter_cherry
)
1906 logger
.addHandler(str_handler
)
1908 if engine_config
["global"].get("log.level"):
1909 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1910 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1912 # logging other modules
1913 for k1
, logname
in {
1914 "message": "nbi.msg",
1915 "database": "nbi.db",
1916 "storage": "nbi.fs",
1918 engine_config
[k1
]["logger_name"] = logname
1919 logger_module
= logging
.getLogger(logname
)
1920 if "logfile" in engine_config
[k1
]:
1921 file_handler
= logging
.handlers
.RotatingFileHandler(
1922 engine_config
[k1
]["logfile"], maxBytes
=100e6
, backupCount
=9, delay
=0
1924 file_handler
.setFormatter(log_formatter_simple
)
1925 logger_module
.addHandler(file_handler
)
1926 if "loglevel" in engine_config
[k1
]:
1927 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1928 # TODO add more entries, e.g.: storage
1929 cherrypy
.tree
.apps
["/osm"].root
.engine
.start(engine_config
)
1930 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.start(engine_config
)
1931 cherrypy
.tree
.apps
["/osm"].root
.engine
.init_db(target_version
=database_version
)
1932 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.init_db(
1933 target_version
=auth_database_version
1936 # start subscriptions thread:
1937 subscription_thread
= SubscriptionThread(
1938 config
=engine_config
, engine
=nbi_server
.engine
1940 subscription_thread
.start()
1941 # Do not capture except SubscriptionException
1943 backend
= engine_config
["authentication"]["backend"]
1945 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1946 nbi_version
, nbi_version_date
, backend
1951 def _stop_service():
1953 Callback function called when cherrypy.engine stops
1954 TODO: Ending database connections.
1956 global subscription_thread
1957 if subscription_thread
:
1958 subscription_thread
.terminate()
1959 subscription_thread
= None
1960 cherrypy
.tree
.apps
["/osm"].root
.engine
.stop()
1961 cherrypy
.log
.error("Stopping osm_nbi")
1964 def nbi(config_file
):
1968 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1969 # 'tools.sessions.on': True,
1970 # 'tools.response_headers.on': True,
1971 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1974 # cherrypy.Server.ssl_module = 'builtin'
1975 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1976 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1977 # cherrypy.Server.thread_pool = 10
1978 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1980 # cherrypy.config.update({'tools.auth_basic.on': True,
1981 # 'tools.auth_basic.realm': 'localhost',
1982 # 'tools.auth_basic.checkpassword': validate_password})
1983 nbi_server
= Server()
1984 cherrypy
.engine
.subscribe("start", _start_service
)
1985 cherrypy
.engine
.subscribe("stop", _stop_service
)
1986 cherrypy
.quickstart(nbi_server
, "/osm", config_file
)
1991 """Usage: {} [options]
1992 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1993 -h|--help: shows this help
1998 # --log-socket-host HOST: send logs to this host")
1999 # --log-socket-port PORT: send logs using this port (default: 9022)")
2002 if __name__
== "__main__":
2004 # load parameters and configuration
2005 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
2006 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
2009 if o
in ("-h", "--help"):
2012 elif o
in ("-c", "--config"):
2014 # elif o == "--log-socket-port":
2015 # log_socket_port = a
2016 # elif o == "--log-socket-host":
2017 # log_socket_host = a
2018 # elif o == "--log-file":
2021 assert False, "Unhandled option"
2023 if not path
.isfile(config_file
):
2025 "configuration file '{}' that not exist".format(config_file
),
2030 for config_file
in (
2031 __file__
[: __file__
.rfind(".")] + ".cfg",
2035 if path
.isfile(config_file
):
2039 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
2044 except getopt
.GetoptError
as e
:
2045 print(str(e
), file=sys
.stderr
)