1cd13b799af624bd63085018526885fe11e3b019
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 "
676 indata
= yaml
.safe_load(cherrypy
.request
.body
)
677 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
679 "application/binary" in cherrypy
.request
.headers
["Content-Type"]
680 or "application/gzip"
681 in cherrypy
.request
.headers
["Content-Type"]
682 or "application/zip" in cherrypy
.request
.headers
["Content-Type"]
683 or "text/plain" in cherrypy
.request
.headers
["Content-Type"]
685 indata
= cherrypy
.request
.body
# .read()
687 "multipart/form-data"
688 in cherrypy
.request
.headers
["Content-Type"]
690 if "descriptor_file" in kwargs
:
691 filecontent
= kwargs
.pop("descriptor_file")
692 if not filecontent
.file:
694 "empty file or content", HTTPStatus
.BAD_REQUEST
696 indata
= filecontent
.file # .read()
697 if filecontent
.content_type
.value
:
698 cherrypy
.request
.headers
[
700 ] = filecontent
.content_type
.value
702 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
703 # "Only 'Content-Type' of type 'application/json' or
704 # 'application/yaml' for input format are available")
705 error_text
= "Invalid yaml format "
706 indata
= yaml
.safe_load(cherrypy
.request
.body
)
707 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
709 error_text
= "Invalid yaml format "
710 indata
= yaml
.safe_load(cherrypy
.request
.body
)
711 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
716 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
719 for k
, v
in kwargs
.items():
720 if isinstance(v
, str):
725 kwargs
[k
] = yaml
.safe_load(v
)
731 or k
.endswith(".gte")
732 or k
.endswith(".lte")
741 elif v
.find(",") > 0:
742 kwargs
[k
] = v
.split(",")
743 elif isinstance(v
, (list, tuple)):
744 for index
in range(0, len(v
)):
749 v
[index
] = yaml
.safe_load(v
[index
])
754 except (ValueError, yaml
.YAMLError
) as exc
:
755 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
756 except KeyError as exc
:
758 "Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
760 except Exception as exc
:
761 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
764 def _format_out(data
, token_info
=None, _format
=None):
766 return string of dictionary data according to requested json, yaml, xml. By default json
767 :param data: response to be sent. Can be a dict, text or file
768 :param token_info: Contains among other username and project
769 :param _format: The format to be set as Content-Type if data is a file
772 accept
= cherrypy
.request
.headers
.get("Accept")
774 if accept
and "text/html" in accept
:
776 data
, cherrypy
.request
, cherrypy
.response
, token_info
778 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
780 elif hasattr(data
, "read"): # file object
782 cherrypy
.response
.headers
["Content-Type"] = _format
783 elif "b" in data
.mode
: # binariy asssumig zip
784 cherrypy
.response
.headers
["Content-Type"] = "application/zip"
786 cherrypy
.response
.headers
["Content-Type"] = "text/plain"
787 # TODO check that cherrypy close file. If not implement pending things to close per thread next
790 if "text/html" in accept
:
792 data
, cherrypy
.request
, cherrypy
.response
, token_info
794 elif "application/yaml" in accept
or "*/*" in accept
:
796 elif "application/json" in accept
or (
797 cherrypy
.response
.status
and cherrypy
.response
.status
>= 300
799 cherrypy
.response
.headers
[
801 ] = "application/json; charset=utf-8"
802 a
= json
.dumps(data
, indent
=4) + "\n"
803 return a
.encode("utf8")
804 cherrypy
.response
.headers
["Content-Type"] = "application/yaml"
805 return yaml
.safe_dump(
809 default_flow_style
=False,
813 ) # , canonical=True, default_style='"'
816 def index(self
, *args
, **kwargs
):
819 if cherrypy
.request
.method
== "GET":
820 token_info
= self
.authenticator
.authorize()
821 outdata
= token_info
# Home page
823 raise cherrypy
.HTTPError(
824 HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
825 "Method {} not allowed for tokens".format(cherrypy
.request
.method
),
828 return self
._format
_out
(outdata
, token_info
)
830 except (EngineException
, AuthException
) as e
:
831 # cherrypy.log("index Exception {}".format(e))
832 cherrypy
.response
.status
= e
.http_code
.value
833 return self
._format
_out
("Welcome to OSM!", token_info
)
836 def version(self
, *args
, **kwargs
):
837 # TODO consider to remove and provide version using the static version file
839 if cherrypy
.request
.method
!= "GET":
841 "Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
845 "Invalid URL or query string for version",
846 HTTPStatus
.METHOD_NOT_ALLOWED
,
848 # TODO include version of other modules, pick up from some kafka admin message
849 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
850 return self
._format
_out
(osm_nbi_version
)
851 except NbiException
as e
:
852 cherrypy
.response
.status
= e
.http_code
.value
854 "code": e
.http_code
.name
,
855 "status": e
.http_code
.value
,
858 return self
._format
_out
(problem_details
, None)
863 "user_domain_name": cherrypy
.tree
.apps
["/osm"]
864 .config
["authentication"]
865 .get("user_domain_name"),
866 "project_domain_name": cherrypy
.tree
.apps
["/osm"]
867 .config
["authentication"]
868 .get("project_domain_name"),
870 return self
._format
_out
(domains
)
871 except NbiException
as e
:
872 cherrypy
.response
.status
= e
.http_code
.value
874 "code": e
.http_code
.name
,
875 "status": e
.http_code
.value
,
878 return self
._format
_out
(problem_details
, None)
881 def _format_login(token_info
):
883 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
885 :param token_info: Dictionary with token content
888 cherrypy
.request
.login
= token_info
.get("username", "-")
889 if token_info
.get("project_name"):
890 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
891 if token_info
.get("id"):
892 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
894 # NS Fault Management
906 if topic
== "alarms":
908 method
= cherrypy
.request
.method
909 role_permission
= self
._check
_valid
_url
_method
(
910 method
, "nsfm", version
, topic
, None, None, *args
912 query_string_operations
= self
._extract
_query
_string
_operations
(
916 self
.authenticator
.authorize(
917 role_permission
, query_string_operations
, None
920 # to handle get request
921 if cherrypy
.request
.method
== "GET":
922 # if request is on basis of uuid
923 if uuid
and uuid
!= "None":
925 alarm
= self
.engine
.db
.get_one("alarms", {"uuid": uuid
})
926 alarm_action
= self
.engine
.db
.get_one(
927 "alarms_action", {"uuid": uuid
}
929 alarm
.update(alarm_action
)
930 vnf
= self
.engine
.db
.get_one(
931 "vnfrs", {"nsr-id-ref": alarm
["tags"]["ns_id"]}
933 alarm
["vnf-id"] = vnf
["_id"]
934 return self
._format
_out
(str(alarm
))
936 return self
._format
_out
("Please provide valid alarm uuid")
937 elif ns_id
and ns_id
!= "None":
938 # if request is on basis of ns_id
940 alarms
= self
.engine
.db
.get_list(
941 "alarms", {"tags.ns_id": ns_id
}
944 alarm_action
= self
.engine
.db
.get_one(
945 "alarms_action", {"uuid": alarm
["uuid"]}
947 alarm
.update(alarm_action
)
948 return self
._format
_out
(str(alarms
))
950 return self
._format
_out
("Please provide valid ns id")
952 # to return only alarm which are related to given project
953 project
= self
.engine
.db
.get_one(
954 "projects", {"name": project_name
}
956 project_id
= project
.get("_id")
957 ns_list
= self
.engine
.db
.get_list(
958 "nsrs", {"_admin.projects_read": project_id
}
962 ns_ids
.append(ns
.get("_id"))
963 alarms
= self
.engine
.db
.get_list("alarms")
967 if alarm
["tags"]["ns_id"] in ns_ids
969 for alrm
in alarm_list
:
970 action
= self
.engine
.db
.get_one(
971 "alarms_action", {"uuid": alrm
.get("uuid")}
974 return self
._format
_out
(str(alarm_list
))
975 # to handle patch request for alarm update
976 elif cherrypy
.request
.method
== "PATCH":
977 data
= yaml
.safe_load(cherrypy
.request
.body
)
979 # check if uuid is valid
980 self
.engine
.db
.get_one("alarms", {"uuid": data
.get("uuid")})
982 return self
._format
_out
("Please provide valid alarm uuid.")
983 if data
.get("is_enable") is not None:
984 if data
.get("is_enable"):
987 alarm_status
= "disabled"
988 self
.engine
.db
.set_one(
990 {"uuid": data
.get("uuid")},
991 {"alarm_status": alarm_status
},
994 self
.engine
.db
.set_one(
996 {"uuid": data
.get("uuid")},
997 {"threshold": data
.get("threshold")},
999 return self
._format
_out
("Alarm updated")
1000 except Exception as e
:
1014 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1015 http_code_name
= e
.http_code
.name
1016 cherrypy
.log("Exception {}".format(e
))
1019 cherrypy
.response
.status
1020 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1021 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1022 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1024 "code": http_code_name
,
1025 "status": http_code_value
,
1028 return self
._format
_out
(problem_details
)
1031 def token(self
, method
, token_id
=None, kwargs
=None):
1033 # self.engine.load_dbase(cherrypy.request.app.config)
1034 indata
= self
._format
_in
(kwargs
)
1035 if not isinstance(indata
, dict):
1037 "Expected application/yaml or application/json Content-Type",
1038 HTTPStatus
.BAD_REQUEST
,
1042 token_info
= self
.authenticator
.authorize()
1044 self
._format
_login
(token_info
)
1046 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
1048 outdata
= self
.authenticator
.get_token_list(token_info
)
1049 elif method
== "POST":
1051 token_info
= self
.authenticator
.authorize()
1055 indata
.update(kwargs
)
1056 # This is needed to log the user when authentication fails
1057 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
1058 outdata
= token_info
= self
.authenticator
.new_token(
1059 token_info
, indata
, cherrypy
.request
.remote
1061 cherrypy
.session
["Authorization"] = outdata
["_id"] # pylint: disable=E1101
1062 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
1064 self
._format
_login
(token_info
)
1065 # password expiry check
1066 if self
.authenticator
.check_password_expiry(outdata
):
1068 "id": outdata
["id"],
1069 "message": "change_password",
1070 "user_id": outdata
["user_id"],
1072 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1073 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1074 elif method
== "DELETE":
1075 if not token_id
and "id" in kwargs
:
1076 token_id
= kwargs
["id"]
1078 token_info
= self
.authenticator
.authorize()
1080 self
._format
_login
(token_info
)
1081 token_id
= token_info
["_id"]
1082 outdata
= self
.authenticator
.del_token(token_id
)
1084 cherrypy
.session
["Authorization"] = "logout" # pylint: disable=E1101
1085 # cherrypy.response.cookie["Authorization"] = token_id
1086 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1089 "Method {} not allowed for token".format(method
),
1090 HTTPStatus
.METHOD_NOT_ALLOWED
,
1092 return self
._format
_out
(outdata
, token_info
)
1095 def test(self
, *args
, **kwargs
):
1096 if not cherrypy
.config
.get("server.enable_test") or (
1097 isinstance(cherrypy
.config
["server.enable_test"], str)
1098 and cherrypy
.config
["server.enable_test"].lower() == "false"
1100 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
1101 return "test URL is disabled"
1103 if args
and args
[0] == "help":
1105 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1106 "sleep/<time>\nmessage/topic\n</pre></html>"
1109 elif args
and args
[0] == "init":
1111 # self.engine.load_dbase(cherrypy.request.app.config)
1112 pid
= self
.authenticator
.create_admin_project()
1113 self
.authenticator
.create_admin_user(pid
)
1114 return "Done. User 'admin', password 'admin' created"
1116 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1117 return self
._format
_out
("Database already initialized")
1118 elif args
and args
[0] == "file":
1119 return cherrypy
.lib
.static
.serve_file(
1120 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1],
1124 elif args
and args
[0] == "file2":
1126 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1]
1128 f
= open(f_path
, "r")
1129 cherrypy
.response
.headers
["Content-type"] = "text/plain"
1132 elif len(args
) == 2 and args
[0] == "db-clear":
1133 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
1134 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
1135 elif len(args
) and args
[0] == "fs-clear":
1137 folders
= (args
[1],)
1139 folders
= self
.engine
.fs
.dir_ls(".")
1140 for folder
in folders
:
1141 self
.engine
.fs
.file_delete(folder
)
1142 return ",".join(folders
) + " folders deleted\n"
1143 elif args
and args
[0] == "login":
1144 if not cherrypy
.request
.headers
.get("Authorization"):
1145 cherrypy
.response
.headers
[
1147 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
1148 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1149 elif args
and args
[0] == "login2":
1150 if not cherrypy
.request
.headers
.get("Authorization"):
1151 cherrypy
.response
.headers
[
1153 ] = 'Bearer realm="Access to OSM site"'
1154 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1155 elif args
and args
[0] == "sleep":
1158 sleep_time
= int(args
[1])
1160 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1161 return self
._format
_out
("Database already initialized")
1162 thread_info
= cherrypy
.thread_data
1164 time
.sleep(sleep_time
)
1166 elif len(args
) >= 2 and args
[0] == "message":
1167 main_topic
= args
[1]
1168 return_text
= "<html><pre>{} ->\n".format(main_topic
)
1170 if cherrypy
.request
.method
== "POST":
1171 to_send
= yaml
.safe_load(cherrypy
.request
.body
)
1172 for k
, v
in to_send
.items():
1173 self
.engine
.msg
.write(main_topic
, k
, v
)
1174 return_text
+= " {}: {}\n".format(k
, v
)
1175 elif cherrypy
.request
.method
== "GET":
1176 for k
, v
in kwargs
.items():
1177 v_dict
= yaml
.safe_load(v
)
1178 self
.engine
.msg
.write(main_topic
, k
, v_dict
)
1179 return_text
+= " {}: {}\n".format(k
, v_dict
)
1180 except Exception as e
:
1181 return_text
+= "Error: " + str(e
)
1182 return_text
+= "</pre></html>\n"
1186 "<html><pre>\nheaders:\n args: {}\n".format(args
)
1187 + " kwargs: {}\n".format(kwargs
)
1188 + " headers: {}\n".format(cherrypy
.request
.headers
)
1189 + " path_info: {}\n".format(cherrypy
.request
.path_info
)
1190 + " query_string: {}\n".format(cherrypy
.request
.query_string
)
1191 + " session: {}\n".format(cherrypy
.session
) # pylint: disable=E1101
1192 + " cookie: {}\n".format(cherrypy
.request
.cookie
)
1193 + " method: {}\n".format(cherrypy
.request
.method
)
1194 + " session: {}\n".format(
1195 cherrypy
.session
.get("fieldname") # pylint: disable=E1101
1199 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
1200 if cherrypy
.request
.body
.length
:
1201 return_text
+= " content: {}\n".format(
1203 cherrypy
.request
.body
.read(
1204 int(cherrypy
.request
.headers
.get("Content-Length", 0))
1209 return_text
+= "thread: {}\n".format(thread_info
)
1210 return_text
+= "</pre></html>"
1214 def _check_valid_url_method(method
, *args
):
1217 "URL must contain at least 'main_topic/version/topic'",
1218 HTTPStatus
.METHOD_NOT_ALLOWED
,
1221 reference
= valid_url_methods
1225 if not isinstance(reference
, dict):
1227 "URL contains unexpected extra items '{}'".format(arg
),
1228 HTTPStatus
.METHOD_NOT_ALLOWED
,
1231 if arg
in reference
:
1232 reference
= reference
[arg
]
1233 elif "<ID>" in reference
:
1234 reference
= reference
["<ID>"]
1235 elif "*" in reference
:
1236 # if there is content
1238 reference
= reference
["*"]
1242 "Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
1244 if "TODO" in reference
and method
in reference
["TODO"]:
1246 "Method {} not supported yet for this URL".format(method
),
1247 HTTPStatus
.NOT_IMPLEMENTED
,
1249 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
1251 "Method {} not supported for this URL".format(method
),
1252 HTTPStatus
.METHOD_NOT_ALLOWED
,
1254 return reference
["ROLE_PERMISSION"] + method
.lower()
1257 def _set_location_header(main_topic
, version
, topic
, id):
1259 Insert response header Location with the URL of created item base on URL params
1266 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1267 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(
1268 main_topic
, version
, topic
, id
1273 def _extract_query_string_operations(kwargs
, method
):
1279 query_string_operations
= []
1281 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1282 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
1283 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
1284 return query_string_operations
1287 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
1289 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1290 Check that users has rights to use them and returs the admin_query
1291 :param token_info: token_info rights obtained by token
1292 :param kwargs: query string input.
1293 :param method: http method: GET, POSST, PUT, ...
1295 :return: admin_query dictionary with keys:
1296 public: True, False or None
1297 force: True or False
1298 project_id: tuple with projects used for accessing an element
1299 set_project: tuple with projects that a created element will belong to
1300 method: show, list, delete, write
1304 "project_id": (token_info
["project_id"],),
1305 "username": token_info
["username"],
1306 "admin": token_info
["admin"],
1308 "allow_show_user_project_role": token_info
["allow_show_user_project_role"],
1312 if "FORCE" in kwargs
:
1314 kwargs
["FORCE"].lower() != "false"
1315 ): # if None or True set force to True
1316 admin_query
["force"] = True
1319 if "PUBLIC" in kwargs
:
1321 kwargs
["PUBLIC"].lower() != "false"
1322 ): # if None or True set public to True
1323 admin_query
["public"] = True
1325 admin_query
["public"] = False
1326 del kwargs
["PUBLIC"]
1328 if "ADMIN" in kwargs
:
1329 behave_as
= kwargs
.pop("ADMIN")
1330 if behave_as
.lower() != "false":
1331 if not token_info
["admin"]:
1333 "Only admin projects can use 'ADMIN' query string",
1334 HTTPStatus
.UNAUTHORIZED
,
1337 not behave_as
or behave_as
.lower() == "true"
1338 ): # convert True, None to empty list
1339 admin_query
["project_id"] = ()
1340 elif isinstance(behave_as
, (list, tuple)):
1341 admin_query
["project_id"] = behave_as
1342 else: # isinstance(behave_as, str)
1343 admin_query
["project_id"] = (behave_as
,)
1344 if "SET_PROJECT" in kwargs
:
1345 set_project
= kwargs
.pop("SET_PROJECT")
1347 admin_query
["set_project"] = list(admin_query
["project_id"])
1349 if isinstance(set_project
, str):
1350 set_project
= (set_project
,)
1351 if admin_query
["project_id"]:
1352 for p
in set_project
:
1353 if p
not in admin_query
["project_id"]:
1355 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1356 "'ADMIN='{p}'".format(p
=p
),
1357 HTTPStatus
.UNAUTHORIZED
,
1359 admin_query
["set_project"] = set_project
1362 # if "PROJECT_READ" in kwargs:
1363 # admin_query["project"] = kwargs.pop("project")
1364 # if admin_query["project"] == token_info["project_id"]:
1367 admin_query
["method"] = "show"
1369 admin_query
["method"] = "list"
1370 elif method
== "DELETE":
1371 admin_query
["method"] = "delete"
1373 admin_query
["method"] = "write"
1393 engine_session
= None
1395 if not main_topic
or not version
or not topic
:
1397 "URL must contain at least 'main_topic/version/topic'",
1398 HTTPStatus
.METHOD_NOT_ALLOWED
,
1400 if main_topic
not in (
1412 "URL main_topic '{}' not supported".format(main_topic
),
1413 HTTPStatus
.METHOD_NOT_ALLOWED
,
1417 "URL version '{}' not supported".format(version
),
1418 HTTPStatus
.METHOD_NOT_ALLOWED
,
1423 and "METHOD" in kwargs
1424 and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1426 method
= kwargs
.pop("METHOD")
1428 method
= cherrypy
.request
.method
1430 role_permission
= self
._check
_valid
_url
_method
(
1431 method
, main_topic
, version
, topic
, _id
, item
, *args
1433 query_string_operations
= self
._extract
_query
_string
_operations
(
1436 if main_topic
== "admin" and topic
== "tokens":
1437 return self
.token(method
, _id
, kwargs
)
1438 token_info
= self
.authenticator
.authorize(
1439 role_permission
, query_string_operations
, _id
1441 if main_topic
== "admin" and topic
== "domains":
1442 return self
.domain()
1443 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
1444 indata
= self
._format
_in
(kwargs
)
1445 engine_topic
= topic
1447 if item
and topic
!= "pm_jobs":
1450 if main_topic
== "nsd":
1451 engine_topic
= "nsds"
1452 elif main_topic
== "vnfpkgm":
1453 engine_topic
= "vnfds"
1454 if topic
== "vnfpkg_op_occs":
1455 engine_topic
= "vnfpkgops"
1456 if topic
== "vnf_packages" and item
== "action":
1457 engine_topic
= "vnfpkgops"
1458 elif main_topic
== "nslcm":
1459 engine_topic
= "nsrs"
1460 if topic
== "ns_lcm_op_occs":
1461 engine_topic
= "nslcmops"
1462 if topic
== "vnfrs" or topic
== "vnf_instances":
1463 engine_topic
= "vnfrs"
1464 elif main_topic
== "vnflcm":
1465 if topic
== "vnf_lcm_op_occs":
1466 engine_topic
= "vnflcmops"
1467 elif main_topic
== "nst":
1468 engine_topic
= "nsts"
1469 elif main_topic
== "nsilcm":
1470 engine_topic
= "nsis"
1471 if topic
== "nsi_lcm_op_occs":
1472 engine_topic
= "nsilcmops"
1473 elif main_topic
== "pdu":
1474 engine_topic
= "pdus"
1476 engine_topic
== "vims"
1477 ): # TODO this is for backward compatibility, it will be removed in the future
1478 engine_topic
= "vim_accounts"
1480 if topic
== "subscriptions":
1481 engine_topic
= main_topic
+ "_" + topic
1493 if item
in ("vnfd", "nsd", "nst"):
1494 path
= "$DESCRIPTOR"
1497 elif item
== "artifacts":
1501 file, _format
= self
.engine
.get_file(
1506 cherrypy
.request
.headers
.get("Accept"),
1510 outdata
= self
.engine
.get_item_list(
1511 engine_session
, engine_topic
, kwargs
, api_req
=True
1514 if item
== "reports":
1515 # TODO check that project_id (_id in this context) has permissions
1518 if "vcaStatusRefresh" in kwargs
:
1519 filter_q
= {"vcaStatusRefresh": kwargs
["vcaStatusRefresh"]}
1520 outdata
= self
.engine
.get_item(
1521 engine_session
, engine_topic
, _id
, filter_q
, True
1524 elif method
== "POST":
1525 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1527 "ns_descriptors_content",
1528 "vnf_packages_content",
1529 "netslice_templates_content",
1531 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1533 _id
, _
= self
.engine
.new_item(
1539 cherrypy
.request
.headers
,
1541 completed
= self
.engine
.upload_content(
1547 cherrypy
.request
.headers
,
1550 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1552 cherrypy
.response
.headers
["Transaction-Id"] = _id
1553 outdata
= {"id": _id
}
1554 elif topic
== "ns_instances_content":
1556 _id
, _
= self
.engine
.new_item(
1557 rollback
, engine_session
, engine_topic
, indata
, kwargs
1560 indata
["lcmOperationType"] = "instantiate"
1561 indata
["nsInstanceId"] = _id
1562 nslcmop_id
, _
= self
.engine
.new_item(
1563 rollback
, engine_session
, "nslcmops", indata
, None
1565 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1566 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1567 elif topic
== "ns_instances" and item
:
1568 indata
["lcmOperationType"] = item
1569 indata
["nsInstanceId"] = _id
1570 _id
, _
= self
.engine
.new_item(
1571 rollback
, engine_session
, "nslcmops", indata
, kwargs
1573 self
._set
_location
_header
(
1574 main_topic
, version
, "ns_lcm_op_occs", _id
1576 outdata
= {"id": _id
}
1577 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1578 elif topic
== "netslice_instances_content":
1579 # creates NetSlice_Instance_record (NSIR)
1580 _id
, _
= self
.engine
.new_item(
1581 rollback
, engine_session
, engine_topic
, indata
, kwargs
1583 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1584 indata
["lcmOperationType"] = "instantiate"
1585 indata
["netsliceInstanceId"] = _id
1586 nsilcmop_id
, _
= self
.engine
.new_item(
1587 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1589 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1590 elif topic
== "netslice_instances" and item
:
1591 indata
["lcmOperationType"] = item
1592 indata
["netsliceInstanceId"] = _id
1593 _id
, _
= self
.engine
.new_item(
1594 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1596 self
._set
_location
_header
(
1597 main_topic
, version
, "nsi_lcm_op_occs", _id
1599 outdata
= {"id": _id
}
1600 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1601 elif topic
== "vnf_packages" and item
== "action":
1602 indata
["lcmOperationType"] = item
1603 indata
["vnfPkgId"] = _id
1604 _id
, _
= self
.engine
.new_item(
1605 rollback
, engine_session
, "vnfpkgops", indata
, kwargs
1607 self
._set
_location
_header
(
1608 main_topic
, version
, "vnfpkg_op_occs", _id
1610 outdata
= {"id": _id
}
1611 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1612 elif topic
== "subscriptions":
1613 _id
, _
= self
.engine
.new_item(
1614 rollback
, engine_session
, engine_topic
, indata
, kwargs
1616 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1618 link
["self"] = cherrypy
.response
.headers
["Location"]
1621 "filter": indata
["filter"],
1622 "callbackUri": indata
["CallbackUri"],
1625 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1626 elif topic
== "vnf_instances" and item
:
1627 indata
["lcmOperationType"] = item
1628 indata
["vnfInstanceId"] = _id
1629 _id
, _
= self
.engine
.new_item(
1630 rollback
, engine_session
, "vnflcmops", indata
, kwargs
1632 self
._set
_location
_header
(
1633 main_topic
, version
, "vnf_lcm_op_occs", _id
1635 outdata
= {"id": _id
}
1636 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1638 _id
, op_id
= self
.engine
.new_item(
1644 cherrypy
.request
.headers
,
1646 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1647 outdata
= {"id": _id
}
1649 outdata
["op_id"] = op_id
1650 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1651 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1653 elif method
== "DELETE":
1655 outdata
= self
.engine
.del_item_list(
1656 engine_session
, engine_topic
, kwargs
1658 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1659 else: # len(args) > 1
1660 # for NS NSI generate an operation
1662 if topic
== "ns_instances_content" and not engine_session
["force"]:
1664 "lcmOperationType": "terminate",
1665 "nsInstanceId": _id
,
1668 op_id
, _
= self
.engine
.new_item(
1669 rollback
, engine_session
, "nslcmops", nslcmop_desc
, kwargs
1672 outdata
= {"_id": op_id
}
1674 topic
== "netslice_instances_content"
1675 and not engine_session
["force"]
1678 "lcmOperationType": "terminate",
1679 "netsliceInstanceId": _id
,
1682 op_id
, _
= self
.engine
.new_item(
1683 rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None
1686 outdata
= {"_id": op_id
}
1687 # if there is not any deletion in process, delete
1689 op_id
= self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1691 outdata
= {"op_id": op_id
}
1692 cherrypy
.response
.status
= (
1693 HTTPStatus
.ACCEPTED
.value
1695 else HTTPStatus
.NO_CONTENT
.value
1698 elif method
in ("PUT", "PATCH"):
1700 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1702 "Nothing to update. Provide payload and/or query string",
1703 HTTPStatus
.BAD_REQUEST
,
1706 item
in ("nsd_content", "package_content", "nst_content")
1709 completed
= self
.engine
.upload_content(
1715 cherrypy
.request
.headers
,
1718 cherrypy
.response
.headers
["Transaction-Id"] = id
1720 op_id
= self
.engine
.edit_item(
1721 engine_session
, engine_topic
, _id
, indata
, kwargs
1725 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1726 outdata
= {"op_id": op_id
}
1728 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1732 "Method {} not allowed".format(method
),
1733 HTTPStatus
.METHOD_NOT_ALLOWED
,
1736 # if Role information changes, it is needed to reload the information of roles
1737 if topic
== "roles" and method
!= "GET":
1738 self
.authenticator
.load_operation_to_allowed_roles()
1742 and method
== "DELETE"
1743 or topic
in ["users", "roles"]
1744 and method
in ["PUT", "PATCH", "DELETE"]
1746 self
.authenticator
.remove_token_from_cache()
1748 return self
._format
_out
(outdata
, token_info
, _format
)
1749 except Exception as e
:
1763 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1764 http_code_name
= e
.http_code
.name
1765 cherrypy
.log("Exception {}".format(e
))
1768 cherrypy
.response
.status
1769 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1770 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1771 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1772 if hasattr(outdata
, "close"): # is an open file
1776 for rollback_item
in rollback
:
1778 if rollback_item
.get("operation") == "set":
1779 self
.engine
.db
.set_one(
1780 rollback_item
["topic"],
1781 {"_id": rollback_item
["_id"]},
1782 rollback_item
["content"],
1783 fail_on_empty
=False,
1785 elif rollback_item
.get("operation") == "del_list":
1786 self
.engine
.db
.del_list(
1787 rollback_item
["topic"],
1788 rollback_item
["filter"],
1791 self
.engine
.db
.del_one(
1792 rollback_item
["topic"],
1793 {"_id": rollback_item
["_id"]},
1794 fail_on_empty
=False,
1796 except Exception as e2
:
1797 rollback_error_text
= "Rollback Exception {}: {}".format(
1800 cherrypy
.log(rollback_error_text
)
1801 error_text
+= ". " + rollback_error_text
1802 # if isinstance(e, MsgException):
1803 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1804 # engine_topic[:-1], method, error_text)
1806 "code": http_code_name
,
1807 "status": http_code_value
,
1808 "detail": error_text
,
1810 return self
._format
_out
(problem_details
, token_info
)
1811 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1814 self
._format
_login
(token_info
)
1815 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1816 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1817 if outdata
.get(logging_id
):
1818 cherrypy
.request
.login
+= ";{}={}".format(
1819 logging_id
, outdata
[logging_id
][:36]
1823 def _start_service():
1825 Callback function called when cherrypy.engine starts
1826 Override configuration with env variables
1827 Set database, storage, message configuration
1828 Init database with admin/admin user password
1831 global subscription_thread
1832 cherrypy
.log
.error("Starting osm_nbi")
1833 # update general cherrypy configuration
1836 engine_config
= cherrypy
.tree
.apps
["/osm"].config
1837 for k
, v
in environ
.items():
1838 if not k
.startswith("OSMNBI_"):
1840 k1
, _
, k2
= k
[7:].lower().partition("_")
1844 # update static configuration
1845 if k
== "OSMNBI_STATIC_DIR":
1846 engine_config
["/static"]["tools.staticdir.dir"] = v
1847 engine_config
["/static"]["tools.staticdir.on"] = True
1848 elif k
== "OSMNBI_SOCKET_PORT" or k
== "OSMNBI_SERVER_PORT":
1849 update_dict
["server.socket_port"] = int(v
)
1850 elif k
== "OSMNBI_SOCKET_HOST" or k
== "OSMNBI_SERVER_HOST":
1851 update_dict
["server.socket_host"] = v
1852 elif k1
in ("server", "test", "auth", "log"):
1853 update_dict
[k1
+ "." + k2
] = v
1854 elif k1
in ("message", "database", "storage", "authentication", "temporal"):
1855 # k2 = k2.replace('_', '.')
1856 if k2
in ("port", "db_port"):
1857 engine_config
[k1
][k2
] = int(v
)
1859 engine_config
[k1
][k2
] = v
1861 except ValueError as e
:
1862 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1863 except Exception as e
:
1865 "WARNING: skipping environ '{}' on exception '{}'".format(k
, e
)
1869 cherrypy
.config
.update(update_dict
)
1870 engine_config
["global"].update(update_dict
)
1873 log_format_simple
= (
1874 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1876 log_formatter_simple
= logging
.Formatter(
1877 log_format_simple
, datefmt
="%Y-%m-%dT%H:%M:%S"
1879 logger_server
= logging
.getLogger("cherrypy.error")
1880 logger_access
= logging
.getLogger("cherrypy.access")
1881 logger_cherry
= logging
.getLogger("cherrypy")
1882 logger_nbi
= logging
.getLogger("nbi")
1884 if "log.file" in engine_config
["global"]:
1885 file_handler
= logging
.handlers
.RotatingFileHandler(
1886 engine_config
["global"]["log.file"], maxBytes
=100e6
, backupCount
=9, delay
=0
1888 file_handler
.setFormatter(log_formatter_simple
)
1889 logger_cherry
.addHandler(file_handler
)
1890 logger_nbi
.addHandler(file_handler
)
1891 # log always to standard output
1892 for format_
, logger
in {
1893 "nbi.server %(filename)s:%(lineno)s": logger_server
,
1894 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1895 "%(name)s %(filename)s:%(lineno)s": logger_nbi
,
1897 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1898 log_formatter_cherry
= logging
.Formatter(
1899 log_format_cherry
, datefmt
="%Y-%m-%dT%H:%M:%S"
1901 str_handler
= logging
.StreamHandler()
1902 str_handler
.setFormatter(log_formatter_cherry
)
1903 logger
.addHandler(str_handler
)
1905 if engine_config
["global"].get("log.level"):
1906 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1907 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1909 # logging other modules
1910 for k1
, logname
in {
1911 "message": "nbi.msg",
1912 "database": "nbi.db",
1913 "storage": "nbi.fs",
1915 engine_config
[k1
]["logger_name"] = logname
1916 logger_module
= logging
.getLogger(logname
)
1917 if "logfile" in engine_config
[k1
]:
1918 file_handler
= logging
.handlers
.RotatingFileHandler(
1919 engine_config
[k1
]["logfile"], maxBytes
=100e6
, backupCount
=9, delay
=0
1921 file_handler
.setFormatter(log_formatter_simple
)
1922 logger_module
.addHandler(file_handler
)
1923 if "loglevel" in engine_config
[k1
]:
1924 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1925 # TODO add more entries, e.g.: storage
1926 cherrypy
.tree
.apps
["/osm"].root
.engine
.start(engine_config
)
1927 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.start(engine_config
)
1928 cherrypy
.tree
.apps
["/osm"].root
.engine
.init_db(target_version
=database_version
)
1929 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.init_db(
1930 target_version
=auth_database_version
1933 # start subscriptions thread:
1934 subscription_thread
= SubscriptionThread(
1935 config
=engine_config
, engine
=nbi_server
.engine
1937 subscription_thread
.start()
1938 # Do not capture except SubscriptionException
1940 WFTemporal
.temporal_api
= (
1941 f
'{engine_config["temporal"]["host"]}:{engine_config["temporal"]["port"]}'
1944 backend
= engine_config
["authentication"]["backend"]
1946 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1947 nbi_version
, nbi_version_date
, backend
1952 def _stop_service():
1954 Callback function called when cherrypy.engine stops
1955 TODO: Ending database connections.
1957 global subscription_thread
1958 if subscription_thread
:
1959 subscription_thread
.terminate()
1960 subscription_thread
= None
1961 cherrypy
.tree
.apps
["/osm"].root
.engine
.stop()
1962 cherrypy
.log
.error("Stopping osm_nbi")
1965 def nbi(config_file
):
1969 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1970 # 'tools.sessions.on': True,
1971 # 'tools.response_headers.on': True,
1972 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1975 # cherrypy.Server.ssl_module = 'builtin'
1976 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1977 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1978 # cherrypy.Server.thread_pool = 10
1979 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1981 # cherrypy.config.update({'tools.auth_basic.on': True,
1982 # 'tools.auth_basic.realm': 'localhost',
1983 # 'tools.auth_basic.checkpassword': validate_password})
1984 nbi_server
= Server()
1985 cherrypy
.engine
.subscribe("start", _start_service
)
1986 cherrypy
.engine
.subscribe("stop", _stop_service
)
1987 cherrypy
.quickstart(nbi_server
, "/osm", config_file
)
1992 """Usage: {} [options]
1993 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1994 -h|--help: shows this help
1999 # --log-socket-host HOST: send logs to this host")
2000 # --log-socket-port PORT: send logs using this port (default: 9022)")
2003 if __name__
== "__main__":
2005 # load parameters and configuration
2006 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
2007 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
2010 if o
in ("-h", "--help"):
2013 elif o
in ("-c", "--config"):
2015 # elif o == "--log-socket-port":
2016 # log_socket_port = a
2017 # elif o == "--log-socket-host":
2018 # log_socket_host = a
2019 # elif o == "--log-file":
2022 assert False, "Unhandled option"
2024 if not path
.isfile(config_file
):
2026 "configuration file '{}' that not exist".format(config_file
),
2031 for config_file
in (
2032 __file__
[: __file__
.rfind(".")] + ".cfg",
2036 if path
.isfile(config_file
):
2040 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
2045 except getopt
.GetoptError
as e
:
2046 print(str(e
), file=sys
.stderr
)