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 osm_common
.wftemporal
import WFTemporal
36 from http
import HTTPStatus
37 from codecs
import getreader
38 from os
import environ
, path
39 from osm_nbi
import version
as nbi_version
, version_date
as nbi_version_date
41 __author__
= "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
43 __version__
= "0.1.3" # file version, not NBI version
44 version_date
= "Aug 2019"
46 database_version
= "1.2"
47 auth_database_version
= "1.0"
48 nbi_server
= None # instance of Server class
49 subscription_thread
= None # instance of SubscriptionThread class
52 North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
53 URL: /osm GET POST PUT DELETE PATCH
55 /ns_descriptors_content O O
61 /artifacts[/<artifactPath>] O
69 /vnf_packages_content O O
73 /package_content O5 O5
76 /artifacts[/<artifactPath>] O5
81 /ns_instances_content O O
95 /vnf_instances (also vnfrs for compatibility) O
111 /vim_accounts (also vims for compatibility) O O
125 /netslice_templates_content O O
127 /netslice_templates O O
131 /artifacts[/<artifactPath>] O
133 /<subscriptionId> X X
136 /netslice_instances_content O O
137 /<SliceInstanceId> O O
138 /netslice_instances O O
139 /<SliceInstanceId> O O
144 /<nsiLcmOpOccId> O O O
146 /<subscriptionId> X X
149 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
150 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
151 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
152 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
154 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
155 item of the array, that is, pass if any item of the array pass the filter.
156 It allows both ne and neq for not equal
157 TODO: 4.3.3 Attribute selectors
158 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
159 (none) … same as “exclude_default”
160 all_fields … all attributes.
161 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
162 conditionally mandatory, and that are not provided in <list>.
163 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
164 are not conditionally mandatory, and that are provided in <list>.
165 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
166 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
167 the particular resource
168 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
169 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
170 present specification for the particular resource, but that are not part of <list>
171 Additionally it admits some administrator values:
172 FORCE: To force operations skipping dependency checkings
173 ADMIN: To act as an administrator or a different project
174 PUBLIC: To get public descriptors or set a descriptor as public
175 SET_PROJECT: To make a descriptor available for other project
177 Header field name Reference Example Descriptions
178 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
179 This header field shall be present if the response is expected to have a non-empty message body.
180 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
181 This header field shall be present if the request has a non-empty message body.
182 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
183 Details are specified in clause 4.5.3.
184 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
185 Header field name Reference Example Descriptions
186 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
187 This header field shall be present if the response has a non-empty message body.
188 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
189 new resource has been created.
190 This header field shall be present if the response status code is 201 or 3xx.
191 In the present document this header field is also used if the response status code is 202 and a new resource was
193 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
194 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
196 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
198 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
199 response, and the total length of the file.
200 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
203 valid_query_string
= ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
204 # ^ Contains possible administrative query string words:
205 # ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
206 # (not owned by my session project).
207 # PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
208 # FORCE=True(by default)|False: Force edition/deletion operations
209 # SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
211 valid_url_methods
= {
212 # contains allowed URL and methods, and the role_permission name
216 "METHODS": ("GET", "POST", "DELETE"),
217 "ROLE_PERMISSION": "tokens:",
218 "<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"},
221 "METHODS": ("GET", "POST"),
222 "ROLE_PERMISSION": "users:",
224 "METHODS": ("GET", "DELETE", "PATCH"),
225 "ROLE_PERMISSION": "users:id:",
229 "METHODS": ("GET", "POST"),
230 "ROLE_PERMISSION": "projects:",
232 "METHODS": ("GET", "DELETE", "PATCH"),
233 "ROLE_PERMISSION": "projects:id:",
237 "METHODS": ("GET", "POST"),
238 "ROLE_PERMISSION": "roles:",
240 "METHODS": ("GET", "DELETE", "PATCH"),
241 "ROLE_PERMISSION": "roles:id:",
245 "METHODS": ("GET", "POST"),
246 "ROLE_PERMISSION": "vims:",
248 "METHODS": ("GET", "DELETE", "PATCH"),
249 "ROLE_PERMISSION": "vims:id:",
253 "METHODS": ("GET", "POST"),
254 "ROLE_PERMISSION": "vim_accounts:",
256 "METHODS": ("GET", "DELETE", "PATCH"),
257 "ROLE_PERMISSION": "vim_accounts:id:",
261 "METHODS": ("GET", "POST"),
262 "ROLE_PERMISSION": "wim_accounts:",
264 "METHODS": ("GET", "DELETE", "PATCH"),
265 "ROLE_PERMISSION": "wim_accounts:id:",
269 "METHODS": ("GET", "POST"),
270 "ROLE_PERMISSION": "sdn_controllers:",
272 "METHODS": ("GET", "DELETE", "PATCH"),
273 "ROLE_PERMISSION": "sdn_controllers:id:",
277 "METHODS": ("GET", "POST"),
278 "ROLE_PERMISSION": "k8sclusters:",
280 "METHODS": ("GET", "DELETE", "PATCH"),
281 "ROLE_PERMISSION": "k8sclusters:id:",
285 "METHODS": ("GET", "POST"),
286 "ROLE_PERMISSION": "vca:",
288 "METHODS": ("GET", "DELETE", "PATCH"),
289 "ROLE_PERMISSION": "vca:id:",
293 "METHODS": ("GET", "POST"),
294 "ROLE_PERMISSION": "k8srepos:",
296 "METHODS": ("GET", "DELETE"),
297 "ROLE_PERMISSION": "k8srepos:id:",
301 "METHODS": ("GET", "POST"),
302 "ROLE_PERMISSION": "osmrepos:",
304 "METHODS": ("GET", "DELETE", "PATCH"),
305 "ROLE_PERMISSION": "osmrepos:id:",
310 "ROLE_PERMISSION": "domains:",
317 "METHODS": ("GET", "POST"),
318 "ROLE_PERMISSION": "pduds:",
320 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
321 "ROLE_PERMISSION": "pduds:id:",
328 "ns_descriptors_content": {
329 "METHODS": ("GET", "POST"),
330 "ROLE_PERMISSION": "nsds:",
332 "METHODS": ("GET", "PUT", "DELETE"),
333 "ROLE_PERMISSION": "nsds:id:",
337 "METHODS": ("GET", "POST"),
338 "ROLE_PERMISSION": "nsds:",
340 "METHODS": ("GET", "DELETE", "PATCH"),
341 "ROLE_PERMISSION": "nsds:id:",
343 "METHODS": ("GET", "PUT"),
344 "ROLE_PERMISSION": "nsds:id:content:",
347 "METHODS": ("GET",), # descriptor inside package
348 "ROLE_PERMISSION": "nsds:id:content:",
352 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
358 "TODO": ("GET", "POST"),
360 "TODO": ("GET", "DELETE", "PATCH"),
361 "pnfd_content": {"TODO": ("GET", "PUT")},
365 "TODO": ("GET", "POST"),
366 "<ID>": {"TODO": ("GET", "DELETE")},
372 "vnf_packages_content": {
373 "METHODS": ("GET", "POST"),
374 "ROLE_PERMISSION": "vnfds:",
376 "METHODS": ("GET", "PUT", "DELETE"),
377 "ROLE_PERMISSION": "vnfds:id:",
381 "METHODS": ("GET", "POST"),
382 "ROLE_PERMISSION": "vnfds:",
384 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
385 "ROLE_PERMISSION": "vnfds:id:",
387 "METHODS": ("GET", "PUT"), # package
388 "ROLE_PERMISSION": "vnfds:id:",
392 "ROLE_PERMISSION": "vnfds:id:upload:",
396 "METHODS": ("GET",), # descriptor inside package
397 "ROLE_PERMISSION": "vnfds:id:content:",
401 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
405 "METHODS": ("POST",),
406 "ROLE_PERMISSION": "vnfds:id:action:",
411 "TODO": ("GET", "POST"),
412 "<ID>": {"TODO": ("GET", "DELETE")},
416 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
417 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"},
423 "ns_instances_content": {
424 "METHODS": ("GET", "POST"),
425 "ROLE_PERMISSION": "ns_instances:",
427 "METHODS": ("GET", "DELETE"),
428 "ROLE_PERMISSION": "ns_instances:id:",
432 "METHODS": ("GET", "POST"),
433 "ROLE_PERMISSION": "ns_instances:",
435 "METHODS": ("GET", "DELETE"),
436 "ROLE_PERMISSION": "ns_instances:id:",
438 "METHODS": ("POST",),
439 "ROLE_PERMISSION": "ns_instances:id:heal:",
442 "METHODS": ("POST",),
443 "ROLE_PERMISSION": "ns_instances:id:scale:",
446 "METHODS": ("POST",),
447 "ROLE_PERMISSION": "ns_instances:id:terminate:",
450 "METHODS": ("POST",),
451 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
454 "METHODS": ("POST",),
455 "ROLE_PERMISSION": "ns_instances:id:migrate:",
458 "METHODS": ("POST",),
459 "ROLE_PERMISSION": "ns_instances:id:action:",
462 "METHODS": ("POST",),
463 "ROLE_PERMISSION": "ns_instances:id:update:",
466 "METHODS": ("POST",),
467 "ROLE_PERMISSION": "ns_instances:id:verticalscale:",
473 "ROLE_PERMISSION": "ns_instances:opps:",
476 "ROLE_PERMISSION": "ns_instances:opps:id:",
481 "ROLE_PERMISSION": "vnf_instances:",
482 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
486 "ROLE_PERMISSION": "vnf_instances:",
487 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
490 "METHODS": ("GET", "POST"),
491 "ROLE_PERMISSION": "ns_subscriptions:",
493 "METHODS": ("GET", "DELETE"),
494 "ROLE_PERMISSION": "ns_subscriptions:id:",
502 "METHODS": ("GET", "POST"),
503 "ROLE_PERMISSION": "vnflcm_instances:",
505 "METHODS": ("GET", "DELETE"),
506 "ROLE_PERMISSION": "vnflcm_instances:id:",
508 "METHODS": ("POST",),
509 "ROLE_PERMISSION": "vnflcm_instances:id:scale:",
512 "METHODS": ("POST",),
513 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:",
516 "METHODS": ("POST",),
517 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:",
523 "ROLE_PERMISSION": "vnf_instances:opps:",
526 "ROLE_PERMISSION": "vnf_instances:opps:id:",
530 "METHODS": ("GET", "POST"),
531 "ROLE_PERMISSION": "vnflcm_subscriptions:",
533 "METHODS": ("GET", "DELETE"),
534 "ROLE_PERMISSION": "vnflcm_subscriptions:id:",
541 "netslice_templates_content": {
542 "METHODS": ("GET", "POST"),
543 "ROLE_PERMISSION": "slice_templates:",
545 "METHODS": ("GET", "PUT", "DELETE"),
546 "ROLE_PERMISSION": "slice_templates:id:",
549 "netslice_templates": {
550 "METHODS": ("GET", "POST"),
551 "ROLE_PERMISSION": "slice_templates:",
553 "METHODS": ("GET", "DELETE"),
555 "ROLE_PERMISSION": "slice_templates:id:",
557 "METHODS": ("GET", "PUT"),
558 "ROLE_PERMISSION": "slice_templates:id:content:",
561 "METHODS": ("GET",), # descriptor inside package
562 "ROLE_PERMISSION": "slice_templates:id:content:",
566 "ROLE_PERMISSION": "slice_templates:id:content:",
572 "TODO": ("GET", "POST"),
573 "<ID>": {"TODO": ("GET", "DELETE")},
579 "netslice_instances_content": {
580 "METHODS": ("GET", "POST"),
581 "ROLE_PERMISSION": "slice_instances:",
583 "METHODS": ("GET", "DELETE"),
584 "ROLE_PERMISSION": "slice_instances:id:",
587 "netslice_instances": {
588 "METHODS": ("GET", "POST"),
589 "ROLE_PERMISSION": "slice_instances:",
591 "METHODS": ("GET", "DELETE"),
592 "ROLE_PERMISSION": "slice_instances:id:",
594 "METHODS": ("POST",),
595 "ROLE_PERMISSION": "slice_instances:id:terminate:",
598 "METHODS": ("POST",),
599 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
602 "METHODS": ("POST",),
603 "ROLE_PERMISSION": "slice_instances:id:action:",
609 "ROLE_PERMISSION": "slice_instances:opps:",
612 "ROLE_PERMISSION": "slice_instances:opps:id:",
624 "ROLE_PERMISSION": "reports:id:",
634 "METHODS": ("GET", "PATCH"),
635 "ROLE_PERMISSION": "alarms:",
637 "METHODS": ("GET", "PATCH"),
638 "ROLE_PERMISSION": "alarms:id:",
646 class NbiException(Exception):
647 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
648 Exception.__init
__(self
, message
)
649 self
.http_code
= http_code
652 class Server(object):
654 # to decode bytes to str
655 reader
= getreader("utf-8")
659 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
660 self
.engine
= Engine(self
.authenticator
)
662 def _format_in(self
, kwargs
):
663 error_text
= "" # error_text must be initialized outside try
666 if cherrypy
.request
.body
.length
:
667 error_text
= "Invalid input format "
669 if "Content-Type" in cherrypy
.request
.headers
:
670 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
671 error_text
= "Invalid json format "
672 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
673 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
674 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
675 error_text
= "Invalid yaml format "
677 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
679 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
681 "application/binary" in cherrypy
.request
.headers
["Content-Type"]
682 or "application/gzip"
683 in cherrypy
.request
.headers
["Content-Type"]
684 or "application/zip" in cherrypy
.request
.headers
["Content-Type"]
685 or "text/plain" in cherrypy
.request
.headers
["Content-Type"]
687 indata
= cherrypy
.request
.body
# .read()
689 "multipart/form-data"
690 in cherrypy
.request
.headers
["Content-Type"]
692 if "descriptor_file" in kwargs
:
693 filecontent
= kwargs
.pop("descriptor_file")
694 if not filecontent
.file:
696 "empty file or content", HTTPStatus
.BAD_REQUEST
698 indata
= filecontent
.file # .read()
699 if filecontent
.content_type
.value
:
700 cherrypy
.request
.headers
[
702 ] = filecontent
.content_type
.value
704 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
705 # "Only 'Content-Type' of type 'application/json' or
706 # 'application/yaml' for input format are available")
707 error_text
= "Invalid yaml format "
709 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
711 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
713 error_text
= "Invalid yaml format "
714 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
715 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
720 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
723 for k
, v
in kwargs
.items():
724 if isinstance(v
, str):
729 kwargs
[k
] = yaml
.load(v
, Loader
=yaml
.SafeLoader
)
735 or k
.endswith(".gte")
736 or k
.endswith(".lte")
745 elif v
.find(",") > 0:
746 kwargs
[k
] = v
.split(",")
747 elif isinstance(v
, (list, tuple)):
748 for index
in range(0, len(v
)):
753 v
[index
] = yaml
.load(v
[index
], Loader
=yaml
.SafeLoader
)
758 except (ValueError, yaml
.YAMLError
) as exc
:
759 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
760 except KeyError as exc
:
762 "Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
764 except Exception as exc
:
765 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
768 def _format_out(data
, token_info
=None, _format
=None):
770 return string of dictionary data according to requested json, yaml, xml. By default json
771 :param data: response to be sent. Can be a dict, text or file
772 :param token_info: Contains among other username and project
773 :param _format: The format to be set as Content-Type if data is a file
776 accept
= cherrypy
.request
.headers
.get("Accept")
778 if accept
and "text/html" in accept
:
780 data
, cherrypy
.request
, cherrypy
.response
, token_info
782 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
784 elif hasattr(data
, "read"): # file object
786 cherrypy
.response
.headers
["Content-Type"] = _format
787 elif "b" in data
.mode
: # binariy asssumig zip
788 cherrypy
.response
.headers
["Content-Type"] = "application/zip"
790 cherrypy
.response
.headers
["Content-Type"] = "text/plain"
791 # TODO check that cherrypy close file. If not implement pending things to close per thread next
794 if "text/html" in accept
:
796 data
, cherrypy
.request
, cherrypy
.response
, token_info
798 elif "application/yaml" in accept
or "*/*" in accept
:
800 elif "application/json" in accept
or (
801 cherrypy
.response
.status
and cherrypy
.response
.status
>= 300
803 cherrypy
.response
.headers
[
805 ] = "application/json; charset=utf-8"
806 a
= json
.dumps(data
, indent
=4) + "\n"
807 return a
.encode("utf8")
808 cherrypy
.response
.headers
["Content-Type"] = "application/yaml"
809 return yaml
.safe_dump(
813 default_flow_style
=False,
817 ) # , canonical=True, default_style='"'
820 def index(self
, *args
, **kwargs
):
823 if cherrypy
.request
.method
== "GET":
824 token_info
= self
.authenticator
.authorize()
825 outdata
= token_info
# Home page
827 raise cherrypy
.HTTPError(
828 HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
829 "Method {} not allowed for tokens".format(cherrypy
.request
.method
),
832 return self
._format
_out
(outdata
, token_info
)
834 except (EngineException
, AuthException
) as e
:
835 # cherrypy.log("index Exception {}".format(e))
836 cherrypy
.response
.status
= e
.http_code
.value
837 return self
._format
_out
("Welcome to OSM!", token_info
)
840 def version(self
, *args
, **kwargs
):
841 # TODO consider to remove and provide version using the static version file
843 if cherrypy
.request
.method
!= "GET":
845 "Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
849 "Invalid URL or query string for version",
850 HTTPStatus
.METHOD_NOT_ALLOWED
,
852 # TODO include version of other modules, pick up from some kafka admin message
853 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
854 return self
._format
_out
(osm_nbi_version
)
855 except NbiException
as e
:
856 cherrypy
.response
.status
= e
.http_code
.value
858 "code": e
.http_code
.name
,
859 "status": e
.http_code
.value
,
862 return self
._format
_out
(problem_details
, None)
867 "user_domain_name": cherrypy
.tree
.apps
["/osm"]
868 .config
["authentication"]
869 .get("user_domain_name"),
870 "project_domain_name": cherrypy
.tree
.apps
["/osm"]
871 .config
["authentication"]
872 .get("project_domain_name"),
874 return self
._format
_out
(domains
)
875 except NbiException
as e
:
876 cherrypy
.response
.status
= e
.http_code
.value
878 "code": e
.http_code
.name
,
879 "status": e
.http_code
.value
,
882 return self
._format
_out
(problem_details
, None)
885 def _format_login(token_info
):
887 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
889 :param token_info: Dictionary with token content
892 cherrypy
.request
.login
= token_info
.get("username", "-")
893 if token_info
.get("project_name"):
894 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
895 if token_info
.get("id"):
896 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
898 # NS Fault Management
910 if topic
== "alarms":
912 method
= cherrypy
.request
.method
913 role_permission
= self
._check
_valid
_url
_method
(
914 method
, "nsfm", version
, topic
, None, None, *args
916 query_string_operations
= self
._extract
_query
_string
_operations
(
920 self
.authenticator
.authorize(
921 role_permission
, query_string_operations
, None
924 # to handle get request
925 if cherrypy
.request
.method
== "GET":
926 # if request is on basis of uuid
927 if uuid
and uuid
!= "None":
929 alarm
= self
.engine
.db
.get_one("alarms", {"uuid": uuid
})
930 alarm_action
= self
.engine
.db
.get_one(
931 "alarms_action", {"uuid": uuid
}
933 alarm
.update(alarm_action
)
934 vnf
= self
.engine
.db
.get_one(
935 "vnfrs", {"nsr-id-ref": alarm
["tags"]["ns_id"]}
937 alarm
["vnf-id"] = vnf
["_id"]
938 return self
._format
_out
(str(alarm
))
940 return self
._format
_out
("Please provide valid alarm uuid")
941 elif ns_id
and ns_id
!= "None":
942 # if request is on basis of ns_id
944 alarms
= self
.engine
.db
.get_list(
945 "alarms", {"tags.ns_id": ns_id
}
948 alarm_action
= self
.engine
.db
.get_one(
949 "alarms_action", {"uuid": alarm
["uuid"]}
951 alarm
.update(alarm_action
)
952 return self
._format
_out
(str(alarms
))
954 return self
._format
_out
("Please provide valid ns id")
956 # to return only alarm which are related to given project
957 project
= self
.engine
.db
.get_one(
958 "projects", {"name": project_name
}
960 project_id
= project
.get("_id")
961 ns_list
= self
.engine
.db
.get_list(
962 "nsrs", {"_admin.projects_read": project_id
}
966 ns_ids
.append(ns
.get("_id"))
967 alarms
= self
.engine
.db
.get_list("alarms")
971 if alarm
["tags"]["ns_id"] in ns_ids
973 for alrm
in alarm_list
:
974 action
= self
.engine
.db
.get_one(
975 "alarms_action", {"uuid": alrm
.get("uuid")}
978 return self
._format
_out
(str(alarm_list
))
979 # to handle patch request for alarm update
980 elif cherrypy
.request
.method
== "PATCH":
981 data
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
983 # check if uuid is valid
984 self
.engine
.db
.get_one("alarms", {"uuid": data
.get("uuid")})
986 return self
._format
_out
("Please provide valid alarm uuid.")
987 if data
.get("is_enable") is not None:
988 if data
.get("is_enable"):
991 alarm_status
= "disabled"
992 self
.engine
.db
.set_one(
994 {"uuid": data
.get("uuid")},
995 {"alarm_status": alarm_status
},
998 self
.engine
.db
.set_one(
1000 {"uuid": data
.get("uuid")},
1001 {"threshold": data
.get("threshold")},
1003 return self
._format
_out
("Alarm updated")
1004 except Exception as e
:
1018 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1019 http_code_name
= e
.http_code
.name
1020 cherrypy
.log("Exception {}".format(e
))
1023 cherrypy
.response
.status
1024 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1025 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1026 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1028 "code": http_code_name
,
1029 "status": http_code_value
,
1032 return self
._format
_out
(problem_details
)
1035 def token(self
, method
, token_id
=None, kwargs
=None):
1037 # self.engine.load_dbase(cherrypy.request.app.config)
1038 indata
= self
._format
_in
(kwargs
)
1039 if not isinstance(indata
, dict):
1041 "Expected application/yaml or application/json Content-Type",
1042 HTTPStatus
.BAD_REQUEST
,
1046 token_info
= self
.authenticator
.authorize()
1048 self
._format
_login
(token_info
)
1050 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
1052 outdata
= self
.authenticator
.get_token_list(token_info
)
1053 elif method
== "POST":
1055 token_info
= self
.authenticator
.authorize()
1059 indata
.update(kwargs
)
1060 # This is needed to log the user when authentication fails
1061 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
1062 outdata
= token_info
= self
.authenticator
.new_token(
1063 token_info
, indata
, cherrypy
.request
.remote
1065 cherrypy
.session
["Authorization"] = outdata
["_id"] # pylint: disable=E1101
1066 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
1068 self
._format
_login
(token_info
)
1069 # password expiry check
1070 if self
.authenticator
.check_password_expiry(outdata
):
1072 "id": outdata
["id"],
1073 "message": "change_password",
1074 "user_id": outdata
["user_id"],
1076 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1077 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1078 elif method
== "DELETE":
1079 if not token_id
and "id" in kwargs
:
1080 token_id
= kwargs
["id"]
1082 token_info
= self
.authenticator
.authorize()
1084 self
._format
_login
(token_info
)
1085 token_id
= token_info
["_id"]
1086 outdata
= self
.authenticator
.del_token(token_id
)
1088 cherrypy
.session
["Authorization"] = "logout" # pylint: disable=E1101
1089 # cherrypy.response.cookie["Authorization"] = token_id
1090 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1093 "Method {} not allowed for token".format(method
),
1094 HTTPStatus
.METHOD_NOT_ALLOWED
,
1096 return self
._format
_out
(outdata
, token_info
)
1099 def test(self
, *args
, **kwargs
):
1100 if not cherrypy
.config
.get("server.enable_test") or (
1101 isinstance(cherrypy
.config
["server.enable_test"], str)
1102 and cherrypy
.config
["server.enable_test"].lower() == "false"
1104 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
1105 return "test URL is disabled"
1107 if args
and args
[0] == "help":
1109 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1110 "sleep/<time>\nmessage/topic\n</pre></html>"
1113 elif args
and args
[0] == "init":
1115 # self.engine.load_dbase(cherrypy.request.app.config)
1116 pid
= self
.authenticator
.create_admin_project()
1117 self
.authenticator
.create_admin_user(pid
)
1118 return "Done. User 'admin', password 'admin' created"
1120 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1121 return self
._format
_out
("Database already initialized")
1122 elif args
and args
[0] == "file":
1123 return cherrypy
.lib
.static
.serve_file(
1124 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1],
1128 elif args
and args
[0] == "file2":
1130 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1]
1132 f
= open(f_path
, "r")
1133 cherrypy
.response
.headers
["Content-type"] = "text/plain"
1136 elif len(args
) == 2 and args
[0] == "db-clear":
1137 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
1138 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
1139 elif len(args
) and args
[0] == "fs-clear":
1141 folders
= (args
[1],)
1143 folders
= self
.engine
.fs
.dir_ls(".")
1144 for folder
in folders
:
1145 self
.engine
.fs
.file_delete(folder
)
1146 return ",".join(folders
) + " folders deleted\n"
1147 elif args
and args
[0] == "login":
1148 if not cherrypy
.request
.headers
.get("Authorization"):
1149 cherrypy
.response
.headers
[
1151 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
1152 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1153 elif args
and args
[0] == "login2":
1154 if not cherrypy
.request
.headers
.get("Authorization"):
1155 cherrypy
.response
.headers
[
1157 ] = 'Bearer realm="Access to OSM site"'
1158 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1159 elif args
and args
[0] == "sleep":
1162 sleep_time
= int(args
[1])
1164 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1165 return self
._format
_out
("Database already initialized")
1166 thread_info
= cherrypy
.thread_data
1168 time
.sleep(sleep_time
)
1170 elif len(args
) >= 2 and args
[0] == "message":
1171 main_topic
= args
[1]
1172 return_text
= "<html><pre>{} ->\n".format(main_topic
)
1174 if cherrypy
.request
.method
== "POST":
1175 to_send
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
1176 for k
, v
in to_send
.items():
1177 self
.engine
.msg
.write(main_topic
, k
, v
)
1178 return_text
+= " {}: {}\n".format(k
, v
)
1179 elif cherrypy
.request
.method
== "GET":
1180 for k
, v
in kwargs
.items():
1181 v_dict
= yaml
.load(v
, Loader
=yaml
.SafeLoader
)
1182 self
.engine
.msg
.write(main_topic
, k
, v_dict
)
1183 return_text
+= " {}: {}\n".format(k
, v_dict
)
1184 except Exception as e
:
1185 return_text
+= "Error: " + str(e
)
1186 return_text
+= "</pre></html>\n"
1190 "<html><pre>\nheaders:\n args: {}\n".format(args
)
1191 + " kwargs: {}\n".format(kwargs
)
1192 + " headers: {}\n".format(cherrypy
.request
.headers
)
1193 + " path_info: {}\n".format(cherrypy
.request
.path_info
)
1194 + " query_string: {}\n".format(cherrypy
.request
.query_string
)
1195 + " session: {}\n".format(cherrypy
.session
) # pylint: disable=E1101
1196 + " cookie: {}\n".format(cherrypy
.request
.cookie
)
1197 + " method: {}\n".format(cherrypy
.request
.method
)
1198 + " session: {}\n".format(
1199 cherrypy
.session
.get("fieldname") # pylint: disable=E1101
1203 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
1204 if cherrypy
.request
.body
.length
:
1205 return_text
+= " content: {}\n".format(
1207 cherrypy
.request
.body
.read(
1208 int(cherrypy
.request
.headers
.get("Content-Length", 0))
1213 return_text
+= "thread: {}\n".format(thread_info
)
1214 return_text
+= "</pre></html>"
1218 def _check_valid_url_method(method
, *args
):
1221 "URL must contain at least 'main_topic/version/topic'",
1222 HTTPStatus
.METHOD_NOT_ALLOWED
,
1225 reference
= valid_url_methods
1229 if not isinstance(reference
, dict):
1231 "URL contains unexpected extra items '{}'".format(arg
),
1232 HTTPStatus
.METHOD_NOT_ALLOWED
,
1235 if arg
in reference
:
1236 reference
= reference
[arg
]
1237 elif "<ID>" in reference
:
1238 reference
= reference
["<ID>"]
1239 elif "*" in reference
:
1240 # if there is content
1242 reference
= reference
["*"]
1246 "Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
1248 if "TODO" in reference
and method
in reference
["TODO"]:
1250 "Method {} not supported yet for this URL".format(method
),
1251 HTTPStatus
.NOT_IMPLEMENTED
,
1253 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
1255 "Method {} not supported for this URL".format(method
),
1256 HTTPStatus
.METHOD_NOT_ALLOWED
,
1258 return reference
["ROLE_PERMISSION"] + method
.lower()
1261 def _set_location_header(main_topic
, version
, topic
, id):
1263 Insert response header Location with the URL of created item base on URL params
1270 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1271 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(
1272 main_topic
, version
, topic
, id
1277 def _extract_query_string_operations(kwargs
, method
):
1283 query_string_operations
= []
1285 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1286 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
1287 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
1288 return query_string_operations
1291 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
1293 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1294 Check that users has rights to use them and returs the admin_query
1295 :param token_info: token_info rights obtained by token
1296 :param kwargs: query string input.
1297 :param method: http method: GET, POSST, PUT, ...
1299 :return: admin_query dictionary with keys:
1300 public: True, False or None
1301 force: True or False
1302 project_id: tuple with projects used for accessing an element
1303 set_project: tuple with projects that a created element will belong to
1304 method: show, list, delete, write
1308 "project_id": (token_info
["project_id"],),
1309 "username": token_info
["username"],
1310 "admin": token_info
["admin"],
1312 "allow_show_user_project_role": token_info
["allow_show_user_project_role"],
1316 if "FORCE" in kwargs
:
1318 kwargs
["FORCE"].lower() != "false"
1319 ): # if None or True set force to True
1320 admin_query
["force"] = True
1323 if "PUBLIC" in kwargs
:
1325 kwargs
["PUBLIC"].lower() != "false"
1326 ): # if None or True set public to True
1327 admin_query
["public"] = True
1329 admin_query
["public"] = False
1330 del kwargs
["PUBLIC"]
1332 if "ADMIN" in kwargs
:
1333 behave_as
= kwargs
.pop("ADMIN")
1334 if behave_as
.lower() != "false":
1335 if not token_info
["admin"]:
1337 "Only admin projects can use 'ADMIN' query string",
1338 HTTPStatus
.UNAUTHORIZED
,
1341 not behave_as
or behave_as
.lower() == "true"
1342 ): # convert True, None to empty list
1343 admin_query
["project_id"] = ()
1344 elif isinstance(behave_as
, (list, tuple)):
1345 admin_query
["project_id"] = behave_as
1346 else: # isinstance(behave_as, str)
1347 admin_query
["project_id"] = (behave_as
,)
1348 if "SET_PROJECT" in kwargs
:
1349 set_project
= kwargs
.pop("SET_PROJECT")
1351 admin_query
["set_project"] = list(admin_query
["project_id"])
1353 if isinstance(set_project
, str):
1354 set_project
= (set_project
,)
1355 if admin_query
["project_id"]:
1356 for p
in set_project
:
1357 if p
not in admin_query
["project_id"]:
1359 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1360 "'ADMIN='{p}'".format(p
=p
),
1361 HTTPStatus
.UNAUTHORIZED
,
1363 admin_query
["set_project"] = set_project
1366 # if "PROJECT_READ" in kwargs:
1367 # admin_query["project"] = kwargs.pop("project")
1368 # if admin_query["project"] == token_info["project_id"]:
1371 admin_query
["method"] = "show"
1373 admin_query
["method"] = "list"
1374 elif method
== "DELETE":
1375 admin_query
["method"] = "delete"
1377 admin_query
["method"] = "write"
1397 engine_session
= None
1399 if not main_topic
or not version
or not topic
:
1401 "URL must contain at least 'main_topic/version/topic'",
1402 HTTPStatus
.METHOD_NOT_ALLOWED
,
1404 if main_topic
not in (
1416 "URL main_topic '{}' not supported".format(main_topic
),
1417 HTTPStatus
.METHOD_NOT_ALLOWED
,
1421 "URL version '{}' not supported".format(version
),
1422 HTTPStatus
.METHOD_NOT_ALLOWED
,
1427 and "METHOD" in kwargs
1428 and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1430 method
= kwargs
.pop("METHOD")
1432 method
= cherrypy
.request
.method
1434 role_permission
= self
._check
_valid
_url
_method
(
1435 method
, main_topic
, version
, topic
, _id
, item
, *args
1437 query_string_operations
= self
._extract
_query
_string
_operations
(
1440 if main_topic
== "admin" and topic
== "tokens":
1441 return self
.token(method
, _id
, kwargs
)
1442 token_info
= self
.authenticator
.authorize(
1443 role_permission
, query_string_operations
, _id
1445 if main_topic
== "admin" and topic
== "domains":
1446 return self
.domain()
1447 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
1448 indata
= self
._format
_in
(kwargs
)
1449 engine_topic
= topic
1451 if item
and topic
!= "pm_jobs":
1454 if main_topic
== "nsd":
1455 engine_topic
= "nsds"
1456 elif main_topic
== "vnfpkgm":
1457 engine_topic
= "vnfds"
1458 if topic
== "vnfpkg_op_occs":
1459 engine_topic
= "vnfpkgops"
1460 if topic
== "vnf_packages" and item
== "action":
1461 engine_topic
= "vnfpkgops"
1462 elif main_topic
== "nslcm":
1463 engine_topic
= "nsrs"
1464 if topic
== "ns_lcm_op_occs":
1465 engine_topic
= "nslcmops"
1466 if topic
== "vnfrs" or topic
== "vnf_instances":
1467 engine_topic
= "vnfrs"
1468 elif main_topic
== "vnflcm":
1469 if topic
== "vnf_lcm_op_occs":
1470 engine_topic
= "vnflcmops"
1471 elif main_topic
== "nst":
1472 engine_topic
= "nsts"
1473 elif main_topic
== "nsilcm":
1474 engine_topic
= "nsis"
1475 if topic
== "nsi_lcm_op_occs":
1476 engine_topic
= "nsilcmops"
1477 elif main_topic
== "pdu":
1478 engine_topic
= "pdus"
1480 engine_topic
== "vims"
1481 ): # TODO this is for backward compatibility, it will be removed in the future
1482 engine_topic
= "vim_accounts"
1484 if topic
== "subscriptions":
1485 engine_topic
= main_topic
+ "_" + topic
1497 if item
in ("vnfd", "nsd", "nst"):
1498 path
= "$DESCRIPTOR"
1501 elif item
== "artifacts":
1505 file, _format
= self
.engine
.get_file(
1510 cherrypy
.request
.headers
.get("Accept"),
1514 outdata
= self
.engine
.get_item_list(
1515 engine_session
, engine_topic
, kwargs
, api_req
=True
1518 if item
== "reports":
1519 # TODO check that project_id (_id in this context) has permissions
1522 if "vcaStatusRefresh" in kwargs
:
1523 filter_q
= {"vcaStatusRefresh": kwargs
["vcaStatusRefresh"]}
1524 outdata
= self
.engine
.get_item(
1525 engine_session
, engine_topic
, _id
, filter_q
, True
1528 elif method
== "POST":
1529 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1531 "ns_descriptors_content",
1532 "vnf_packages_content",
1533 "netslice_templates_content",
1535 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1537 _id
, _
= self
.engine
.new_item(
1543 cherrypy
.request
.headers
,
1545 completed
= self
.engine
.upload_content(
1551 cherrypy
.request
.headers
,
1554 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1556 cherrypy
.response
.headers
["Transaction-Id"] = _id
1557 outdata
= {"id": _id
}
1558 elif topic
== "ns_instances_content":
1560 _id
, _
= self
.engine
.new_item(
1561 rollback
, engine_session
, engine_topic
, indata
, kwargs
1564 indata
["lcmOperationType"] = "instantiate"
1565 indata
["nsInstanceId"] = _id
1566 nslcmop_id
, _
= self
.engine
.new_item(
1567 rollback
, engine_session
, "nslcmops", indata
, None
1569 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1570 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1571 elif topic
== "ns_instances" and item
:
1572 indata
["lcmOperationType"] = item
1573 indata
["nsInstanceId"] = _id
1574 _id
, _
= self
.engine
.new_item(
1575 rollback
, engine_session
, "nslcmops", indata
, kwargs
1577 self
._set
_location
_header
(
1578 main_topic
, version
, "ns_lcm_op_occs", _id
1580 outdata
= {"id": _id
}
1581 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1582 elif topic
== "netslice_instances_content":
1583 # creates NetSlice_Instance_record (NSIR)
1584 _id
, _
= self
.engine
.new_item(
1585 rollback
, engine_session
, engine_topic
, indata
, kwargs
1587 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1588 indata
["lcmOperationType"] = "instantiate"
1589 indata
["netsliceInstanceId"] = _id
1590 nsilcmop_id
, _
= self
.engine
.new_item(
1591 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1593 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1594 elif topic
== "netslice_instances" and item
:
1595 indata
["lcmOperationType"] = item
1596 indata
["netsliceInstanceId"] = _id
1597 _id
, _
= self
.engine
.new_item(
1598 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1600 self
._set
_location
_header
(
1601 main_topic
, version
, "nsi_lcm_op_occs", _id
1603 outdata
= {"id": _id
}
1604 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1605 elif topic
== "vnf_packages" and item
== "action":
1606 indata
["lcmOperationType"] = item
1607 indata
["vnfPkgId"] = _id
1608 _id
, _
= self
.engine
.new_item(
1609 rollback
, engine_session
, "vnfpkgops", indata
, kwargs
1611 self
._set
_location
_header
(
1612 main_topic
, version
, "vnfpkg_op_occs", _id
1614 outdata
= {"id": _id
}
1615 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1616 elif topic
== "subscriptions":
1617 _id
, _
= self
.engine
.new_item(
1618 rollback
, engine_session
, engine_topic
, indata
, kwargs
1620 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1622 link
["self"] = cherrypy
.response
.headers
["Location"]
1625 "filter": indata
["filter"],
1626 "callbackUri": indata
["CallbackUri"],
1629 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1630 elif topic
== "vnf_instances" and item
:
1631 indata
["lcmOperationType"] = item
1632 indata
["vnfInstanceId"] = _id
1633 _id
, _
= self
.engine
.new_item(
1634 rollback
, engine_session
, "vnflcmops", indata
, kwargs
1636 self
._set
_location
_header
(
1637 main_topic
, version
, "vnf_lcm_op_occs", _id
1639 outdata
= {"id": _id
}
1640 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1642 _id
, op_id
= self
.engine
.new_item(
1648 cherrypy
.request
.headers
,
1650 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1651 outdata
= {"id": _id
}
1653 outdata
["op_id"] = op_id
1654 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1655 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1657 elif method
== "DELETE":
1659 outdata
= self
.engine
.del_item_list(
1660 engine_session
, engine_topic
, kwargs
1662 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1663 else: # len(args) > 1
1664 # for NS NSI generate an operation
1666 if topic
== "ns_instances_content" and not engine_session
["force"]:
1668 "lcmOperationType": "terminate",
1669 "nsInstanceId": _id
,
1672 op_id
, _
= self
.engine
.new_item(
1673 rollback
, engine_session
, "nslcmops", nslcmop_desc
, kwargs
1676 outdata
= {"_id": op_id
}
1678 topic
== "netslice_instances_content"
1679 and not engine_session
["force"]
1682 "lcmOperationType": "terminate",
1683 "netsliceInstanceId": _id
,
1686 op_id
, _
= self
.engine
.new_item(
1687 rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None
1690 outdata
= {"_id": op_id
}
1691 # if there is not any deletion in process, delete
1693 op_id
= self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1695 outdata
= {"op_id": op_id
}
1696 cherrypy
.response
.status
= (
1697 HTTPStatus
.ACCEPTED
.value
1699 else HTTPStatus
.NO_CONTENT
.value
1702 elif method
in ("PUT", "PATCH"):
1704 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1706 "Nothing to update. Provide payload and/or query string",
1707 HTTPStatus
.BAD_REQUEST
,
1710 item
in ("nsd_content", "package_content", "nst_content")
1713 completed
= self
.engine
.upload_content(
1719 cherrypy
.request
.headers
,
1722 cherrypy
.response
.headers
["Transaction-Id"] = id
1724 op_id
= self
.engine
.edit_item(
1725 engine_session
, engine_topic
, _id
, indata
, kwargs
1729 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1730 outdata
= {"op_id": op_id
}
1732 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1736 "Method {} not allowed".format(method
),
1737 HTTPStatus
.METHOD_NOT_ALLOWED
,
1740 # if Role information changes, it is needed to reload the information of roles
1741 if topic
== "roles" and method
!= "GET":
1742 self
.authenticator
.load_operation_to_allowed_roles()
1746 and method
== "DELETE"
1747 or topic
in ["users", "roles"]
1748 and method
in ["PUT", "PATCH", "DELETE"]
1750 self
.authenticator
.remove_token_from_cache()
1752 return self
._format
_out
(outdata
, token_info
, _format
)
1753 except Exception as e
:
1767 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1768 http_code_name
= e
.http_code
.name
1769 cherrypy
.log("Exception {}".format(e
))
1772 cherrypy
.response
.status
1773 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1774 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1775 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1776 if hasattr(outdata
, "close"): # is an open file
1780 for rollback_item
in rollback
:
1782 if rollback_item
.get("operation") == "set":
1783 self
.engine
.db
.set_one(
1784 rollback_item
["topic"],
1785 {"_id": rollback_item
["_id"]},
1786 rollback_item
["content"],
1787 fail_on_empty
=False,
1789 elif rollback_item
.get("operation") == "del_list":
1790 self
.engine
.db
.del_list(
1791 rollback_item
["topic"],
1792 rollback_item
["filter"],
1795 self
.engine
.db
.del_one(
1796 rollback_item
["topic"],
1797 {"_id": rollback_item
["_id"]},
1798 fail_on_empty
=False,
1800 except Exception as e2
:
1801 rollback_error_text
= "Rollback Exception {}: {}".format(
1804 cherrypy
.log(rollback_error_text
)
1805 error_text
+= ". " + rollback_error_text
1806 # if isinstance(e, MsgException):
1807 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1808 # engine_topic[:-1], method, error_text)
1810 "code": http_code_name
,
1811 "status": http_code_value
,
1812 "detail": error_text
,
1814 return self
._format
_out
(problem_details
, token_info
)
1815 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1818 self
._format
_login
(token_info
)
1819 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1820 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1821 if outdata
.get(logging_id
):
1822 cherrypy
.request
.login
+= ";{}={}".format(
1823 logging_id
, outdata
[logging_id
][:36]
1827 def _start_service():
1829 Callback function called when cherrypy.engine starts
1830 Override configuration with env variables
1831 Set database, storage, message configuration
1832 Init database with admin/admin user password
1835 global subscription_thread
1836 cherrypy
.log
.error("Starting osm_nbi")
1837 # update general cherrypy configuration
1840 engine_config
= cherrypy
.tree
.apps
["/osm"].config
1841 for k
, v
in environ
.items():
1842 if not k
.startswith("OSMNBI_"):
1844 k1
, _
, k2
= k
[7:].lower().partition("_")
1848 # update static configuration
1849 if k
== "OSMNBI_STATIC_DIR":
1850 engine_config
["/static"]["tools.staticdir.dir"] = v
1851 engine_config
["/static"]["tools.staticdir.on"] = True
1852 elif k
== "OSMNBI_SOCKET_PORT" or k
== "OSMNBI_SERVER_PORT":
1853 update_dict
["server.socket_port"] = int(v
)
1854 elif k
== "OSMNBI_SOCKET_HOST" or k
== "OSMNBI_SERVER_HOST":
1855 update_dict
["server.socket_host"] = v
1856 elif k1
in ("server", "test", "auth", "log"):
1857 update_dict
[k1
+ "." + k2
] = v
1858 elif k1
in ("message", "database", "storage", "authentication", "temporal"):
1859 # k2 = k2.replace('_', '.')
1860 if k2
in ("port", "db_port"):
1861 engine_config
[k1
][k2
] = int(v
)
1863 engine_config
[k1
][k2
] = v
1865 except ValueError as e
:
1866 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1867 except Exception as e
:
1869 "WARNING: skipping environ '{}' on exception '{}'".format(k
, e
)
1873 cherrypy
.config
.update(update_dict
)
1874 engine_config
["global"].update(update_dict
)
1877 log_format_simple
= (
1878 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1880 log_formatter_simple
= logging
.Formatter(
1881 log_format_simple
, datefmt
="%Y-%m-%dT%H:%M:%S"
1883 logger_server
= logging
.getLogger("cherrypy.error")
1884 logger_access
= logging
.getLogger("cherrypy.access")
1885 logger_cherry
= logging
.getLogger("cherrypy")
1886 logger_nbi
= logging
.getLogger("nbi")
1888 if "log.file" in engine_config
["global"]:
1889 file_handler
= logging
.handlers
.RotatingFileHandler(
1890 engine_config
["global"]["log.file"], maxBytes
=100e6
, backupCount
=9, delay
=0
1892 file_handler
.setFormatter(log_formatter_simple
)
1893 logger_cherry
.addHandler(file_handler
)
1894 logger_nbi
.addHandler(file_handler
)
1895 # log always to standard output
1896 for format_
, logger
in {
1897 "nbi.server %(filename)s:%(lineno)s": logger_server
,
1898 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1899 "%(name)s %(filename)s:%(lineno)s": logger_nbi
,
1901 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1902 log_formatter_cherry
= logging
.Formatter(
1903 log_format_cherry
, datefmt
="%Y-%m-%dT%H:%M:%S"
1905 str_handler
= logging
.StreamHandler()
1906 str_handler
.setFormatter(log_formatter_cherry
)
1907 logger
.addHandler(str_handler
)
1909 if engine_config
["global"].get("log.level"):
1910 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1911 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1913 # logging other modules
1914 for k1
, logname
in {
1915 "message": "nbi.msg",
1916 "database": "nbi.db",
1917 "storage": "nbi.fs",
1919 engine_config
[k1
]["logger_name"] = logname
1920 logger_module
= logging
.getLogger(logname
)
1921 if "logfile" in engine_config
[k1
]:
1922 file_handler
= logging
.handlers
.RotatingFileHandler(
1923 engine_config
[k1
]["logfile"], maxBytes
=100e6
, backupCount
=9, delay
=0
1925 file_handler
.setFormatter(log_formatter_simple
)
1926 logger_module
.addHandler(file_handler
)
1927 if "loglevel" in engine_config
[k1
]:
1928 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1929 # TODO add more entries, e.g.: storage
1930 cherrypy
.tree
.apps
["/osm"].root
.engine
.start(engine_config
)
1931 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.start(engine_config
)
1932 cherrypy
.tree
.apps
["/osm"].root
.engine
.init_db(target_version
=database_version
)
1933 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.init_db(
1934 target_version
=auth_database_version
1937 # start subscriptions thread:
1938 subscription_thread
= SubscriptionThread(
1939 config
=engine_config
, engine
=nbi_server
.engine
1941 subscription_thread
.start()
1942 # Do not capture except SubscriptionException
1944 WFTemporal
.temporal_api
= (
1945 f
'{engine_config["temporal"]["host"]}:{engine_config["temporal"]["port"]}'
1948 backend
= engine_config
["authentication"]["backend"]
1950 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1951 nbi_version
, nbi_version_date
, backend
1956 def _stop_service():
1958 Callback function called when cherrypy.engine stops
1959 TODO: Ending database connections.
1961 global subscription_thread
1962 if subscription_thread
:
1963 subscription_thread
.terminate()
1964 subscription_thread
= None
1965 cherrypy
.tree
.apps
["/osm"].root
.engine
.stop()
1966 cherrypy
.log
.error("Stopping osm_nbi")
1969 def nbi(config_file
):
1973 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1974 # 'tools.sessions.on': True,
1975 # 'tools.response_headers.on': True,
1976 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1979 # cherrypy.Server.ssl_module = 'builtin'
1980 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1981 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1982 # cherrypy.Server.thread_pool = 10
1983 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1985 # cherrypy.config.update({'tools.auth_basic.on': True,
1986 # 'tools.auth_basic.realm': 'localhost',
1987 # 'tools.auth_basic.checkpassword': validate_password})
1988 nbi_server
= Server()
1989 cherrypy
.engine
.subscribe("start", _start_service
)
1990 cherrypy
.engine
.subscribe("stop", _stop_service
)
1991 cherrypy
.quickstart(nbi_server
, "/osm", config_file
)
1996 """Usage: {} [options]
1997 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1998 -h|--help: shows this help
2003 # --log-socket-host HOST: send logs to this host")
2004 # --log-socket-port PORT: send logs using this port (default: 9022)")
2007 if __name__
== "__main__":
2009 # load parameters and configuration
2010 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
2011 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
2014 if o
in ("-h", "--help"):
2017 elif o
in ("-c", "--config"):
2019 # elif o == "--log-socket-port":
2020 # log_socket_port = a
2021 # elif o == "--log-socket-host":
2022 # log_socket_host = a
2023 # elif o == "--log-file":
2026 assert False, "Unhandled option"
2028 if not path
.isfile(config_file
):
2030 "configuration file '{}' that not exist".format(config_file
),
2035 for config_file
in (
2036 __file__
[: __file__
.rfind(".")] + ".cfg",
2040 if path
.isfile(config_file
):
2044 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
2049 except getopt
.GetoptError
as e
:
2050 print(str(e
), file=sys
.stderr
)