8fa61522bac0388a227189339e7192e674c53560
2 # -*- coding: utf-8 -*-
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
21 import osm_nbi
.html_out
as html
23 import logging
.handlers
27 from osm_nbi
.authconn
import AuthException
, AuthconnException
28 from osm_nbi
.auth
import Authenticator
29 from osm_nbi
.engine
import Engine
, EngineException
30 from osm_nbi
.subscriptions
import SubscriptionThread
31 from osm_nbi
.validation
import ValidationError
32 from osm_common
.dbbase
import DbException
33 from osm_common
.fsbase
import FsException
34 from osm_common
.msgbase
import MsgException
35 from http
import HTTPStatus
36 from codecs
import getreader
37 from os
import environ
, path
38 from osm_nbi
import version
as nbi_version
, version_date
as nbi_version_date
40 __author__
= "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
42 __version__
= "0.1.3" # file version, not NBI version
43 version_date
= "Aug 2019"
45 database_version
= "1.2"
46 auth_database_version
= "1.0"
47 nbi_server
= None # instance of Server class
48 subscription_thread
= None # instance of SubscriptionThread class
51 North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
52 URL: /osm GET POST PUT DELETE PATCH
54 /ns_descriptors_content O O
60 /artifacts[/<artifactPath>] O
68 /vnf_packages_content O O
72 /package_content O5 O5
75 /artifacts[/<artifactPath>] O5
80 /ns_instances_content O O
94 /vnf_instances (also vnfrs for compatibility) O
110 /vim_accounts (also vims for compatibility) O O
124 /netslice_templates_content O O
126 /netslice_templates O O
130 /artifacts[/<artifactPath>] O
132 /<subscriptionId> X X
135 /netslice_instances_content O O
136 /<SliceInstanceId> O O
137 /netslice_instances O O
138 /<SliceInstanceId> O O
143 /<nsiLcmOpOccId> O O O
145 /<subscriptionId> X X
148 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
149 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
150 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
151 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
153 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
154 item of the array, that is, pass if any item of the array pass the filter.
155 It allows both ne and neq for not equal
156 TODO: 4.3.3 Attribute selectors
157 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
158 (none) … same as “exclude_default”
159 all_fields … all attributes.
160 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
161 conditionally mandatory, and that are not provided in <list>.
162 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
163 are not conditionally mandatory, and that are provided in <list>.
164 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
165 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
166 the particular resource
167 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
168 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
169 present specification for the particular resource, but that are not part of <list>
170 Additionally it admits some administrator values:
171 FORCE: To force operations skipping dependency checkings
172 ADMIN: To act as an administrator or a different project
173 PUBLIC: To get public descriptors or set a descriptor as public
174 SET_PROJECT: To make a descriptor available for other project
176 Header field name Reference Example Descriptions
177 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
178 This header field shall be present if the response is expected to have a non-empty message body.
179 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
180 This header field shall be present if the request has a non-empty message body.
181 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
182 Details are specified in clause 4.5.3.
183 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
184 Header field name Reference Example Descriptions
185 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
186 This header field shall be present if the response has a non-empty message body.
187 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
188 new resource has been created.
189 This header field shall be present if the response status code is 201 or 3xx.
190 In the present document this header field is also used if the response status code is 202 and a new resource was
192 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
193 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
195 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
197 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
198 response, and the total length of the file.
199 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
202 valid_query_string
= ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
203 # ^ Contains possible administrative query string words:
204 # ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
205 # (not owned by my session project).
206 # PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
207 # FORCE=True(by default)|False: Force edition/deletion operations
208 # SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
210 valid_url_methods
= {
211 # contains allowed URL and methods, and the role_permission name
215 "METHODS": ("GET", "POST", "DELETE"),
216 "ROLE_PERMISSION": "tokens:",
217 "<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"},
220 "METHODS": ("GET", "POST"),
221 "ROLE_PERMISSION": "users:",
223 "METHODS": ("GET", "DELETE", "PATCH"),
224 "ROLE_PERMISSION": "users:id:",
228 "METHODS": ("GET", "POST"),
229 "ROLE_PERMISSION": "projects:",
231 "METHODS": ("GET", "DELETE", "PATCH"),
232 "ROLE_PERMISSION": "projects:id:",
236 "METHODS": ("GET", "POST"),
237 "ROLE_PERMISSION": "roles:",
239 "METHODS": ("GET", "DELETE", "PATCH"),
240 "ROLE_PERMISSION": "roles:id:",
244 "METHODS": ("GET", "POST"),
245 "ROLE_PERMISSION": "vims:",
247 "METHODS": ("GET", "DELETE", "PATCH"),
248 "ROLE_PERMISSION": "vims:id:",
252 "METHODS": ("GET", "POST"),
253 "ROLE_PERMISSION": "vim_accounts:",
255 "METHODS": ("GET", "DELETE", "PATCH"),
256 "ROLE_PERMISSION": "vim_accounts:id:",
260 "METHODS": ("GET", "POST"),
261 "ROLE_PERMISSION": "wim_accounts:",
263 "METHODS": ("GET", "DELETE", "PATCH"),
264 "ROLE_PERMISSION": "wim_accounts:id:",
268 "METHODS": ("GET", "POST"),
269 "ROLE_PERMISSION": "sdn_controllers:",
271 "METHODS": ("GET", "DELETE", "PATCH"),
272 "ROLE_PERMISSION": "sdn_controllers:id:",
276 "METHODS": ("GET", "POST"),
277 "ROLE_PERMISSION": "k8sclusters:",
279 "METHODS": ("GET", "DELETE", "PATCH"),
280 "ROLE_PERMISSION": "k8sclusters:id:",
284 "METHODS": ("GET", "POST"),
285 "ROLE_PERMISSION": "vca:",
287 "METHODS": ("GET", "DELETE", "PATCH"),
288 "ROLE_PERMISSION": "vca:id:",
292 "METHODS": ("GET", "POST"),
293 "ROLE_PERMISSION": "k8srepos:",
295 "METHODS": ("GET", "DELETE"),
296 "ROLE_PERMISSION": "k8srepos:id:",
300 "METHODS": ("GET", "POST"),
301 "ROLE_PERMISSION": "osmrepos:",
303 "METHODS": ("GET", "DELETE", "PATCH"),
304 "ROLE_PERMISSION": "osmrepos:id:",
309 "ROLE_PERMISSION": "domains:",
316 "METHODS": ("GET", "POST"),
317 "ROLE_PERMISSION": "pduds:",
319 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
320 "ROLE_PERMISSION": "pduds:id:",
327 "ns_descriptors_content": {
328 "METHODS": ("GET", "POST"),
329 "ROLE_PERMISSION": "nsds:",
331 "METHODS": ("GET", "PUT", "DELETE"),
332 "ROLE_PERMISSION": "nsds:id:",
336 "METHODS": ("GET", "POST"),
337 "ROLE_PERMISSION": "nsds:",
339 "METHODS": ("GET", "DELETE", "PATCH"),
340 "ROLE_PERMISSION": "nsds:id:",
342 "METHODS": ("GET", "PUT"),
343 "ROLE_PERMISSION": "nsds:id:content:",
346 "METHODS": ("GET",), # descriptor inside package
347 "ROLE_PERMISSION": "nsds:id:content:",
351 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
357 "TODO": ("GET", "POST"),
359 "TODO": ("GET", "DELETE", "PATCH"),
360 "pnfd_content": {"TODO": ("GET", "PUT")},
364 "TODO": ("GET", "POST"),
365 "<ID>": {"TODO": ("GET", "DELETE")},
371 "vnf_packages_content": {
372 "METHODS": ("GET", "POST"),
373 "ROLE_PERMISSION": "vnfds:",
375 "METHODS": ("GET", "PUT", "DELETE"),
376 "ROLE_PERMISSION": "vnfds:id:",
380 "METHODS": ("GET", "POST"),
381 "ROLE_PERMISSION": "vnfds:",
383 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
384 "ROLE_PERMISSION": "vnfds:id:",
386 "METHODS": ("GET", "PUT"), # package
387 "ROLE_PERMISSION": "vnfds:id:",
391 "ROLE_PERMISSION": "vnfds:id:upload:",
395 "METHODS": ("GET",), # descriptor inside package
396 "ROLE_PERMISSION": "vnfds:id:content:",
400 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
404 "METHODS": ("POST",),
405 "ROLE_PERMISSION": "vnfds:id:action:",
410 "TODO": ("GET", "POST"),
411 "<ID>": {"TODO": ("GET", "DELETE")},
415 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
416 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"},
422 "ns_instances_content": {
423 "METHODS": ("GET", "POST"),
424 "ROLE_PERMISSION": "ns_instances:",
426 "METHODS": ("GET", "DELETE"),
427 "ROLE_PERMISSION": "ns_instances:id:",
431 "METHODS": ("GET", "POST"),
432 "ROLE_PERMISSION": "ns_instances:",
434 "METHODS": ("GET", "DELETE"),
435 "ROLE_PERMISSION": "ns_instances:id:",
437 "METHODS": ("POST",),
438 "ROLE_PERMISSION": "ns_instances:id:heal:",
441 "METHODS": ("POST",),
442 "ROLE_PERMISSION": "ns_instances:id:scale:",
445 "METHODS": ("POST",),
446 "ROLE_PERMISSION": "ns_instances:id:terminate:",
449 "METHODS": ("POST",),
450 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
453 "METHODS": ("POST",),
454 "ROLE_PERMISSION": "ns_instances:id:migrate:",
457 "METHODS": ("POST",),
458 "ROLE_PERMISSION": "ns_instances:id:action:",
461 "METHODS": ("POST",),
462 "ROLE_PERMISSION": "ns_instances:id:update:",
465 "METHODS": ("POST",),
466 "ROLE_PERMISSION": "ns_instances:id:verticalscale:",
472 "ROLE_PERMISSION": "ns_instances:opps:",
475 "ROLE_PERMISSION": "ns_instances:opps:id:",
480 "ROLE_PERMISSION": "vnf_instances:",
481 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
485 "ROLE_PERMISSION": "vnf_instances:",
486 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
489 "METHODS": ("GET", "POST"),
490 "ROLE_PERMISSION": "ns_subscriptions:",
492 "METHODS": ("GET", "DELETE"),
493 "ROLE_PERMISSION": "ns_subscriptions:id:",
501 "METHODS": ("GET", "POST"),
502 "ROLE_PERMISSION": "vnflcm_instances:",
504 "METHODS": ("GET", "DELETE"),
505 "ROLE_PERMISSION": "vnflcm_instances:id:",
507 "METHODS": ("POST",),
508 "ROLE_PERMISSION": "vnflcm_instances:id:scale:",
511 "METHODS": ("POST",),
512 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:",
515 "METHODS": ("POST",),
516 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:",
522 "ROLE_PERMISSION": "vnf_instances:opps:",
525 "ROLE_PERMISSION": "vnf_instances:opps:id:",
529 "METHODS": ("GET", "POST"),
530 "ROLE_PERMISSION": "vnflcm_subscriptions:",
532 "METHODS": ("GET", "DELETE"),
533 "ROLE_PERMISSION": "vnflcm_subscriptions:id:",
540 "netslice_templates_content": {
541 "METHODS": ("GET", "POST"),
542 "ROLE_PERMISSION": "slice_templates:",
544 "METHODS": ("GET", "PUT", "DELETE"),
545 "ROLE_PERMISSION": "slice_templates:id:",
548 "netslice_templates": {
549 "METHODS": ("GET", "POST"),
550 "ROLE_PERMISSION": "slice_templates:",
552 "METHODS": ("GET", "DELETE"),
554 "ROLE_PERMISSION": "slice_templates:id:",
556 "METHODS": ("GET", "PUT"),
557 "ROLE_PERMISSION": "slice_templates:id:content:",
560 "METHODS": ("GET",), # descriptor inside package
561 "ROLE_PERMISSION": "slice_templates:id:content:",
565 "ROLE_PERMISSION": "slice_templates:id:content:",
571 "TODO": ("GET", "POST"),
572 "<ID>": {"TODO": ("GET", "DELETE")},
578 "netslice_instances_content": {
579 "METHODS": ("GET", "POST"),
580 "ROLE_PERMISSION": "slice_instances:",
582 "METHODS": ("GET", "DELETE"),
583 "ROLE_PERMISSION": "slice_instances:id:",
586 "netslice_instances": {
587 "METHODS": ("GET", "POST"),
588 "ROLE_PERMISSION": "slice_instances:",
590 "METHODS": ("GET", "DELETE"),
591 "ROLE_PERMISSION": "slice_instances:id:",
593 "METHODS": ("POST",),
594 "ROLE_PERMISSION": "slice_instances:id:terminate:",
597 "METHODS": ("POST",),
598 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
601 "METHODS": ("POST",),
602 "ROLE_PERMISSION": "slice_instances:id:action:",
608 "ROLE_PERMISSION": "slice_instances:opps:",
611 "ROLE_PERMISSION": "slice_instances:opps:id:",
623 "ROLE_PERMISSION": "reports:id:",
633 "METHODS": ("GET", "PATCH"),
634 "ROLE_PERMISSION": "alarms:",
636 "METHODS": ("GET", "PATCH"),
637 "ROLE_PERMISSION": "alarms:id:",
645 class NbiException(Exception):
646 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
647 Exception.__init
__(self
, message
)
648 self
.http_code
= http_code
651 class Server(object):
653 # to decode bytes to str
654 reader
= getreader("utf-8")
658 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
659 self
.engine
= Engine(self
.authenticator
)
661 def _format_in(self
, kwargs
):
662 error_text
= "" # error_text must be initialized outside try
665 if cherrypy
.request
.body
.length
:
666 error_text
= "Invalid input format "
668 if "Content-Type" in cherrypy
.request
.headers
:
669 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
670 error_text
= "Invalid json format "
671 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
672 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
673 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
674 error_text
= "Invalid yaml format "
675 indata
= yaml
.safe_load(cherrypy
.request
.body
)
676 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
678 "application/binary" in cherrypy
.request
.headers
["Content-Type"]
679 or "application/gzip"
680 in cherrypy
.request
.headers
["Content-Type"]
681 or "application/zip" in cherrypy
.request
.headers
["Content-Type"]
682 or "text/plain" in cherrypy
.request
.headers
["Content-Type"]
684 indata
= cherrypy
.request
.body
# .read()
686 "multipart/form-data"
687 in cherrypy
.request
.headers
["Content-Type"]
689 if "descriptor_file" in kwargs
:
690 filecontent
= kwargs
.pop("descriptor_file")
691 if not filecontent
.file:
693 "empty file or content", HTTPStatus
.BAD_REQUEST
695 indata
= filecontent
.file # .read()
696 if filecontent
.content_type
.value
:
697 cherrypy
.request
.headers
[
699 ] = filecontent
.content_type
.value
701 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
702 # "Only 'Content-Type' of type 'application/json' or
703 # 'application/yaml' for input format are available")
704 error_text
= "Invalid yaml format "
705 indata
= yaml
.safe_load(cherrypy
.request
.body
)
706 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
708 error_text
= "Invalid yaml format "
709 indata
= yaml
.safe_load(cherrypy
.request
.body
)
710 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
715 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
718 for k
, v
in kwargs
.items():
719 if isinstance(v
, str):
724 kwargs
[k
] = yaml
.safe_load(v
)
730 or k
.endswith(".gte")
731 or k
.endswith(".lte")
740 elif v
.find(",") > 0:
741 kwargs
[k
] = v
.split(",")
742 elif isinstance(v
, (list, tuple)):
743 for index
in range(0, len(v
)):
748 v
[index
] = yaml
.safe_load(v
[index
])
753 except (ValueError, yaml
.YAMLError
) as exc
:
754 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
755 except KeyError as exc
:
757 "Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
759 except Exception as exc
:
760 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
763 def _format_out(data
, token_info
=None, _format
=None):
765 return string of dictionary data according to requested json, yaml, xml. By default json
766 :param data: response to be sent. Can be a dict, text or file
767 :param token_info: Contains among other username and project
768 :param _format: The format to be set as Content-Type if data is a file
771 accept
= cherrypy
.request
.headers
.get("Accept")
773 if accept
and "text/html" in accept
:
775 data
, cherrypy
.request
, cherrypy
.response
, token_info
777 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
779 elif hasattr(data
, "read"): # file object
781 cherrypy
.response
.headers
["Content-Type"] = _format
782 elif "b" in data
.mode
: # binariy asssumig zip
783 cherrypy
.response
.headers
["Content-Type"] = "application/zip"
785 cherrypy
.response
.headers
["Content-Type"] = "text/plain"
786 # TODO check that cherrypy close file. If not implement pending things to close per thread next
789 if "text/html" in accept
:
791 data
, cherrypy
.request
, cherrypy
.response
, token_info
793 elif "application/yaml" in accept
or "*/*" in accept
:
795 elif "application/json" in accept
or (
796 cherrypy
.response
.status
and cherrypy
.response
.status
>= 300
798 cherrypy
.response
.headers
[
800 ] = "application/json; charset=utf-8"
801 a
= json
.dumps(data
, indent
=4) + "\n"
802 return a
.encode("utf8")
803 cherrypy
.response
.headers
["Content-Type"] = "application/yaml"
804 return yaml
.safe_dump(
808 default_flow_style
=False,
812 ) # , canonical=True, default_style='"'
815 def index(self
, *args
, **kwargs
):
818 if cherrypy
.request
.method
== "GET":
819 token_info
= self
.authenticator
.authorize()
820 outdata
= token_info
# Home page
822 raise cherrypy
.HTTPError(
823 HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
824 "Method {} not allowed for tokens".format(cherrypy
.request
.method
),
827 return self
._format
_out
(outdata
, token_info
)
829 except (EngineException
, AuthException
) as e
:
830 # cherrypy.log("index Exception {}".format(e))
831 cherrypy
.response
.status
= e
.http_code
.value
832 return self
._format
_out
("Welcome to OSM!", token_info
)
835 def version(self
, *args
, **kwargs
):
836 # TODO consider to remove and provide version using the static version file
838 if cherrypy
.request
.method
!= "GET":
840 "Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
844 "Invalid URL or query string for version",
845 HTTPStatus
.METHOD_NOT_ALLOWED
,
847 # TODO include version of other modules, pick up from some kafka admin message
848 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
849 return self
._format
_out
(osm_nbi_version
)
850 except NbiException
as e
:
851 cherrypy
.response
.status
= e
.http_code
.value
853 "code": e
.http_code
.name
,
854 "status": e
.http_code
.value
,
857 return self
._format
_out
(problem_details
, None)
862 "user_domain_name": cherrypy
.tree
.apps
["/osm"]
863 .config
["authentication"]
864 .get("user_domain_name"),
865 "project_domain_name": cherrypy
.tree
.apps
["/osm"]
866 .config
["authentication"]
867 .get("project_domain_name"),
869 return self
._format
_out
(domains
)
870 except NbiException
as e
:
871 cherrypy
.response
.status
= e
.http_code
.value
873 "code": e
.http_code
.name
,
874 "status": e
.http_code
.value
,
877 return self
._format
_out
(problem_details
, None)
880 def _format_login(token_info
):
882 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
884 :param token_info: Dictionary with token content
887 cherrypy
.request
.login
= token_info
.get("username", "-")
888 if token_info
.get("project_name"):
889 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
890 if token_info
.get("id"):
891 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
893 # NS Fault Management
905 if topic
== "alarms":
907 method
= cherrypy
.request
.method
908 role_permission
= self
._check
_valid
_url
_method
(
909 method
, "nsfm", version
, topic
, None, None, *args
911 query_string_operations
= self
._extract
_query
_string
_operations
(
915 self
.authenticator
.authorize(
916 role_permission
, query_string_operations
, None
919 # to handle get request
920 if cherrypy
.request
.method
== "GET":
921 # if request is on basis of uuid
922 if uuid
and uuid
!= "None":
924 alarm
= self
.engine
.db
.get_one("alarms", {"uuid": uuid
})
925 alarm_action
= self
.engine
.db
.get_one(
926 "alarms_action", {"uuid": uuid
}
928 alarm
.update(alarm_action
)
929 vnf
= self
.engine
.db
.get_one(
930 "vnfrs", {"nsr-id-ref": alarm
["tags"]["ns_id"]}
932 alarm
["vnf-id"] = vnf
["_id"]
933 return self
._format
_out
(str(alarm
))
935 return self
._format
_out
("Please provide valid alarm uuid")
936 elif ns_id
and ns_id
!= "None":
937 # if request is on basis of ns_id
939 alarms
= self
.engine
.db
.get_list(
940 "alarms", {"tags.ns_id": ns_id
}
943 alarm_action
= self
.engine
.db
.get_one(
944 "alarms_action", {"uuid": alarm
["uuid"]}
946 alarm
.update(alarm_action
)
947 return self
._format
_out
(str(alarms
))
949 return self
._format
_out
("Please provide valid ns id")
951 # to return only alarm which are related to given project
952 project
= self
.engine
.db
.get_one(
953 "projects", {"name": project_name
}
955 project_id
= project
.get("_id")
956 ns_list
= self
.engine
.db
.get_list(
957 "nsrs", {"_admin.projects_read": project_id
}
961 ns_ids
.append(ns
.get("_id"))
962 alarms
= self
.engine
.db
.get_list("alarms")
966 if alarm
["tags"]["ns_id"] in ns_ids
968 for alrm
in alarm_list
:
969 action
= self
.engine
.db
.get_one(
970 "alarms_action", {"uuid": alrm
.get("uuid")}
973 return self
._format
_out
(str(alarm_list
))
974 # to handle patch request for alarm update
975 elif cherrypy
.request
.method
== "PATCH":
976 data
= yaml
.safe_load(cherrypy
.request
.body
)
978 # check if uuid is valid
979 self
.engine
.db
.get_one("alarms", {"uuid": data
.get("uuid")})
981 return self
._format
_out
("Please provide valid alarm uuid.")
982 if data
.get("is_enable") is not None:
983 if data
.get("is_enable"):
986 alarm_status
= "disabled"
987 self
.engine
.db
.set_one(
989 {"uuid": data
.get("uuid")},
990 {"alarm_status": alarm_status
},
993 self
.engine
.db
.set_one(
995 {"uuid": data
.get("uuid")},
996 {"threshold": data
.get("threshold")},
998 return self
._format
_out
("Alarm updated")
999 except Exception as e
:
1013 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1014 http_code_name
= e
.http_code
.name
1015 cherrypy
.log("Exception {}".format(e
))
1018 cherrypy
.response
.status
1019 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1020 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1021 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1023 "code": http_code_name
,
1024 "status": http_code_value
,
1027 return self
._format
_out
(problem_details
)
1030 def token(self
, method
, token_id
=None, kwargs
=None):
1032 # self.engine.load_dbase(cherrypy.request.app.config)
1033 indata
= self
._format
_in
(kwargs
)
1034 if not isinstance(indata
, dict):
1036 "Expected application/yaml or application/json Content-Type",
1037 HTTPStatus
.BAD_REQUEST
,
1041 token_info
= self
.authenticator
.authorize()
1043 self
._format
_login
(token_info
)
1045 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
1047 outdata
= self
.authenticator
.get_token_list(token_info
)
1048 elif method
== "POST":
1050 token_info
= self
.authenticator
.authorize()
1054 indata
.update(kwargs
)
1055 # This is needed to log the user when authentication fails
1056 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
1057 outdata
= token_info
= self
.authenticator
.new_token(
1058 token_info
, indata
, cherrypy
.request
.remote
1060 cherrypy
.session
["Authorization"] = outdata
["_id"] # pylint: disable=E1101
1061 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
1063 self
._format
_login
(token_info
)
1064 # password expiry check
1065 if self
.authenticator
.check_password_expiry(outdata
):
1067 "id": outdata
["id"],
1068 "message": "change_password",
1069 "user_id": outdata
["user_id"],
1071 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1072 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1073 elif method
== "DELETE":
1074 if not token_id
and "id" in kwargs
:
1075 token_id
= kwargs
["id"]
1077 token_info
= self
.authenticator
.authorize()
1079 self
._format
_login
(token_info
)
1080 token_id
= token_info
["_id"]
1081 outdata
= self
.authenticator
.del_token(token_id
)
1083 cherrypy
.session
["Authorization"] = "logout" # pylint: disable=E1101
1084 # cherrypy.response.cookie["Authorization"] = token_id
1085 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1088 "Method {} not allowed for token".format(method
),
1089 HTTPStatus
.METHOD_NOT_ALLOWED
,
1091 return self
._format
_out
(outdata
, token_info
)
1094 def test(self
, *args
, **kwargs
):
1095 if not cherrypy
.config
.get("server.enable_test") or (
1096 isinstance(cherrypy
.config
["server.enable_test"], str)
1097 and cherrypy
.config
["server.enable_test"].lower() == "false"
1099 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
1100 return "test URL is disabled"
1102 if args
and args
[0] == "help":
1104 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1105 "sleep/<time>\nmessage/topic\n</pre></html>"
1108 elif args
and args
[0] == "init":
1110 # self.engine.load_dbase(cherrypy.request.app.config)
1111 pid
= self
.authenticator
.create_admin_project()
1112 self
.authenticator
.create_admin_user(pid
)
1113 return "Done. User 'admin', password 'admin' created"
1115 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1116 return self
._format
_out
("Database already initialized")
1117 elif args
and args
[0] == "file":
1118 return cherrypy
.lib
.static
.serve_file(
1119 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1],
1123 elif args
and args
[0] == "file2":
1125 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1]
1127 f
= open(f_path
, "r")
1128 cherrypy
.response
.headers
["Content-type"] = "text/plain"
1131 elif len(args
) == 2 and args
[0] == "db-clear":
1132 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
1133 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
1134 elif len(args
) and args
[0] == "fs-clear":
1136 folders
= (args
[1],)
1138 folders
= self
.engine
.fs
.dir_ls(".")
1139 for folder
in folders
:
1140 self
.engine
.fs
.file_delete(folder
)
1141 return ",".join(folders
) + " folders deleted\n"
1142 elif args
and args
[0] == "login":
1143 if not cherrypy
.request
.headers
.get("Authorization"):
1144 cherrypy
.response
.headers
[
1146 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
1147 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1148 elif args
and args
[0] == "login2":
1149 if not cherrypy
.request
.headers
.get("Authorization"):
1150 cherrypy
.response
.headers
[
1152 ] = 'Bearer realm="Access to OSM site"'
1153 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1154 elif args
and args
[0] == "sleep":
1157 sleep_time
= int(args
[1])
1159 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1160 return self
._format
_out
("Database already initialized")
1161 thread_info
= cherrypy
.thread_data
1163 time
.sleep(sleep_time
)
1165 elif len(args
) >= 2 and args
[0] == "message":
1166 main_topic
= args
[1]
1167 return_text
= "<html><pre>{} ->\n".format(main_topic
)
1169 if cherrypy
.request
.method
== "POST":
1170 to_send
= yaml
.safe_load(cherrypy
.request
.body
)
1171 for k
, v
in to_send
.items():
1172 self
.engine
.msg
.write(main_topic
, k
, v
)
1173 return_text
+= " {}: {}\n".format(k
, v
)
1174 elif cherrypy
.request
.method
== "GET":
1175 for k
, v
in kwargs
.items():
1176 v_dict
= yaml
.safe_load(v
)
1177 self
.engine
.msg
.write(main_topic
, k
, v_dict
)
1178 return_text
+= " {}: {}\n".format(k
, v_dict
)
1179 except Exception as e
:
1180 return_text
+= "Error: " + str(e
)
1181 return_text
+= "</pre></html>\n"
1185 "<html><pre>\nheaders:\n args: {}\n".format(args
)
1186 + " kwargs: {}\n".format(kwargs
)
1187 + " headers: {}\n".format(cherrypy
.request
.headers
)
1188 + " path_info: {}\n".format(cherrypy
.request
.path_info
)
1189 + " query_string: {}\n".format(cherrypy
.request
.query_string
)
1190 + " session: {}\n".format(cherrypy
.session
) # pylint: disable=E1101
1191 + " cookie: {}\n".format(cherrypy
.request
.cookie
)
1192 + " method: {}\n".format(cherrypy
.request
.method
)
1193 + " session: {}\n".format(
1194 cherrypy
.session
.get("fieldname") # pylint: disable=E1101
1198 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
1199 if cherrypy
.request
.body
.length
:
1200 return_text
+= " content: {}\n".format(
1202 cherrypy
.request
.body
.read(
1203 int(cherrypy
.request
.headers
.get("Content-Length", 0))
1208 return_text
+= "thread: {}\n".format(thread_info
)
1209 return_text
+= "</pre></html>"
1213 def _check_valid_url_method(method
, *args
):
1216 "URL must contain at least 'main_topic/version/topic'",
1217 HTTPStatus
.METHOD_NOT_ALLOWED
,
1220 reference
= valid_url_methods
1224 if not isinstance(reference
, dict):
1226 "URL contains unexpected extra items '{}'".format(arg
),
1227 HTTPStatus
.METHOD_NOT_ALLOWED
,
1230 if arg
in reference
:
1231 reference
= reference
[arg
]
1232 elif "<ID>" in reference
:
1233 reference
= reference
["<ID>"]
1234 elif "*" in reference
:
1235 # if there is content
1237 reference
= reference
["*"]
1241 "Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
1243 if "TODO" in reference
and method
in reference
["TODO"]:
1245 "Method {} not supported yet for this URL".format(method
),
1246 HTTPStatus
.NOT_IMPLEMENTED
,
1248 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
1250 "Method {} not supported for this URL".format(method
),
1251 HTTPStatus
.METHOD_NOT_ALLOWED
,
1253 return reference
["ROLE_PERMISSION"] + method
.lower()
1256 def _set_location_header(main_topic
, version
, topic
, id):
1258 Insert response header Location with the URL of created item base on URL params
1265 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1266 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(
1267 main_topic
, version
, topic
, id
1272 def _extract_query_string_operations(kwargs
, method
):
1278 query_string_operations
= []
1280 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1281 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
1282 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
1283 return query_string_operations
1286 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
1288 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1289 Check that users has rights to use them and returs the admin_query
1290 :param token_info: token_info rights obtained by token
1291 :param kwargs: query string input.
1292 :param method: http method: GET, POSST, PUT, ...
1294 :return: admin_query dictionary with keys:
1295 public: True, False or None
1296 force: True or False
1297 project_id: tuple with projects used for accessing an element
1298 set_project: tuple with projects that a created element will belong to
1299 method: show, list, delete, write
1303 "project_id": (token_info
["project_id"],),
1304 "username": token_info
["username"],
1305 "admin": token_info
["admin"],
1307 "allow_show_user_project_role": token_info
["allow_show_user_project_role"],
1311 if "FORCE" in kwargs
:
1313 kwargs
["FORCE"].lower() != "false"
1314 ): # if None or True set force to True
1315 admin_query
["force"] = True
1318 if "PUBLIC" in kwargs
:
1320 kwargs
["PUBLIC"].lower() != "false"
1321 ): # if None or True set public to True
1322 admin_query
["public"] = True
1324 admin_query
["public"] = False
1325 del kwargs
["PUBLIC"]
1327 if "ADMIN" in kwargs
:
1328 behave_as
= kwargs
.pop("ADMIN")
1329 if behave_as
.lower() != "false":
1330 if not token_info
["admin"]:
1332 "Only admin projects can use 'ADMIN' query string",
1333 HTTPStatus
.UNAUTHORIZED
,
1336 not behave_as
or behave_as
.lower() == "true"
1337 ): # convert True, None to empty list
1338 admin_query
["project_id"] = ()
1339 elif isinstance(behave_as
, (list, tuple)):
1340 admin_query
["project_id"] = behave_as
1341 else: # isinstance(behave_as, str)
1342 admin_query
["project_id"] = (behave_as
,)
1343 if "SET_PROJECT" in kwargs
:
1344 set_project
= kwargs
.pop("SET_PROJECT")
1346 admin_query
["set_project"] = list(admin_query
["project_id"])
1348 if isinstance(set_project
, str):
1349 set_project
= (set_project
,)
1350 if admin_query
["project_id"]:
1351 for p
in set_project
:
1352 if p
not in admin_query
["project_id"]:
1354 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1355 "'ADMIN='{p}'".format(p
=p
),
1356 HTTPStatus
.UNAUTHORIZED
,
1358 admin_query
["set_project"] = set_project
1361 # if "PROJECT_READ" in kwargs:
1362 # admin_query["project"] = kwargs.pop("project")
1363 # if admin_query["project"] == token_info["project_id"]:
1366 admin_query
["method"] = "show"
1368 admin_query
["method"] = "list"
1369 elif method
== "DELETE":
1370 admin_query
["method"] = "delete"
1372 admin_query
["method"] = "write"
1392 engine_session
= None
1394 if not main_topic
or not version
or not topic
:
1396 "URL must contain at least 'main_topic/version/topic'",
1397 HTTPStatus
.METHOD_NOT_ALLOWED
,
1399 if main_topic
not in (
1411 "URL main_topic '{}' not supported".format(main_topic
),
1412 HTTPStatus
.METHOD_NOT_ALLOWED
,
1416 "URL version '{}' not supported".format(version
),
1417 HTTPStatus
.METHOD_NOT_ALLOWED
,
1422 and "METHOD" in kwargs
1423 and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1425 method
= kwargs
.pop("METHOD")
1427 method
= cherrypy
.request
.method
1429 role_permission
= self
._check
_valid
_url
_method
(
1430 method
, main_topic
, version
, topic
, _id
, item
, *args
1432 query_string_operations
= self
._extract
_query
_string
_operations
(
1435 if main_topic
== "admin" and topic
== "tokens":
1436 return self
.token(method
, _id
, kwargs
)
1437 token_info
= self
.authenticator
.authorize(
1438 role_permission
, query_string_operations
, _id
1440 if main_topic
== "admin" and topic
== "domains":
1441 return self
.domain()
1442 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
1443 indata
= self
._format
_in
(kwargs
)
1444 engine_topic
= topic
1446 if item
and topic
!= "pm_jobs":
1449 if main_topic
== "nsd":
1450 engine_topic
= "nsds"
1451 elif main_topic
== "vnfpkgm":
1452 engine_topic
= "vnfds"
1453 if topic
== "vnfpkg_op_occs":
1454 engine_topic
= "vnfpkgops"
1455 if topic
== "vnf_packages" and item
== "action":
1456 engine_topic
= "vnfpkgops"
1457 elif main_topic
== "nslcm":
1458 engine_topic
= "nsrs"
1459 if topic
== "ns_lcm_op_occs":
1460 engine_topic
= "nslcmops"
1461 if topic
== "vnfrs" or topic
== "vnf_instances":
1462 engine_topic
= "vnfrs"
1463 elif main_topic
== "vnflcm":
1464 if topic
== "vnf_lcm_op_occs":
1465 engine_topic
= "vnflcmops"
1466 elif main_topic
== "nst":
1467 engine_topic
= "nsts"
1468 elif main_topic
== "nsilcm":
1469 engine_topic
= "nsis"
1470 if topic
== "nsi_lcm_op_occs":
1471 engine_topic
= "nsilcmops"
1472 elif main_topic
== "pdu":
1473 engine_topic
= "pdus"
1475 engine_topic
== "vims"
1476 ): # TODO this is for backward compatibility, it will be removed in the future
1477 engine_topic
= "vim_accounts"
1479 if topic
== "subscriptions":
1480 engine_topic
= main_topic
+ "_" + topic
1492 if item
in ("vnfd", "nsd", "nst"):
1493 path
= "$DESCRIPTOR"
1496 elif item
== "artifacts":
1500 file, _format
= self
.engine
.get_file(
1505 cherrypy
.request
.headers
.get("Accept"),
1509 outdata
= self
.engine
.get_item_list(
1510 engine_session
, engine_topic
, kwargs
, api_req
=True
1513 if item
== "reports":
1514 # TODO check that project_id (_id in this context) has permissions
1517 if "vcaStatusRefresh" in kwargs
:
1518 filter_q
= {"vcaStatusRefresh": kwargs
["vcaStatusRefresh"]}
1519 outdata
= self
.engine
.get_item(
1520 engine_session
, engine_topic
, _id
, filter_q
, True
1523 elif method
== "POST":
1524 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1526 "ns_descriptors_content",
1527 "vnf_packages_content",
1528 "netslice_templates_content",
1530 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1532 _id
, _
= self
.engine
.new_item(
1538 cherrypy
.request
.headers
,
1540 completed
= self
.engine
.upload_content(
1546 cherrypy
.request
.headers
,
1549 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1551 cherrypy
.response
.headers
["Transaction-Id"] = _id
1552 outdata
= {"id": _id
}
1553 elif topic
== "ns_instances_content":
1555 _id
, _
= self
.engine
.new_item(
1556 rollback
, engine_session
, engine_topic
, indata
, kwargs
1559 indata
["lcmOperationType"] = "instantiate"
1560 indata
["nsInstanceId"] = _id
1561 nslcmop_id
, _
= self
.engine
.new_item(
1562 rollback
, engine_session
, "nslcmops", indata
, None
1564 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1565 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1566 elif topic
== "ns_instances" and item
:
1567 indata
["lcmOperationType"] = item
1568 indata
["nsInstanceId"] = _id
1569 _id
, _
= self
.engine
.new_item(
1570 rollback
, engine_session
, "nslcmops", indata
, kwargs
1572 self
._set
_location
_header
(
1573 main_topic
, version
, "ns_lcm_op_occs", _id
1575 outdata
= {"id": _id
}
1576 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1577 elif topic
== "netslice_instances_content":
1578 # creates NetSlice_Instance_record (NSIR)
1579 _id
, _
= self
.engine
.new_item(
1580 rollback
, engine_session
, engine_topic
, indata
, kwargs
1582 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1583 indata
["lcmOperationType"] = "instantiate"
1584 indata
["netsliceInstanceId"] = _id
1585 nsilcmop_id
, _
= self
.engine
.new_item(
1586 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1588 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1589 elif topic
== "netslice_instances" and item
:
1590 indata
["lcmOperationType"] = item
1591 indata
["netsliceInstanceId"] = _id
1592 _id
, _
= self
.engine
.new_item(
1593 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1595 self
._set
_location
_header
(
1596 main_topic
, version
, "nsi_lcm_op_occs", _id
1598 outdata
= {"id": _id
}
1599 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1600 elif topic
== "vnf_packages" and item
== "action":
1601 indata
["lcmOperationType"] = item
1602 indata
["vnfPkgId"] = _id
1603 _id
, _
= self
.engine
.new_item(
1604 rollback
, engine_session
, "vnfpkgops", indata
, kwargs
1606 self
._set
_location
_header
(
1607 main_topic
, version
, "vnfpkg_op_occs", _id
1609 outdata
= {"id": _id
}
1610 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1611 elif topic
== "subscriptions":
1612 _id
, _
= self
.engine
.new_item(
1613 rollback
, engine_session
, engine_topic
, indata
, kwargs
1615 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1617 link
["self"] = cherrypy
.response
.headers
["Location"]
1620 "filter": indata
["filter"],
1621 "callbackUri": indata
["CallbackUri"],
1624 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1625 elif topic
== "vnf_instances" and item
:
1626 indata
["lcmOperationType"] = item
1627 indata
["vnfInstanceId"] = _id
1628 _id
, _
= self
.engine
.new_item(
1629 rollback
, engine_session
, "vnflcmops", indata
, kwargs
1631 self
._set
_location
_header
(
1632 main_topic
, version
, "vnf_lcm_op_occs", _id
1634 outdata
= {"id": _id
}
1635 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1637 _id
, op_id
= self
.engine
.new_item(
1643 cherrypy
.request
.headers
,
1645 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1646 outdata
= {"id": _id
}
1648 outdata
["op_id"] = op_id
1649 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1650 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1652 elif method
== "DELETE":
1654 outdata
= self
.engine
.del_item_list(
1655 engine_session
, engine_topic
, kwargs
1657 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1658 else: # len(args) > 1
1659 # for NS NSI generate an operation
1661 if topic
== "ns_instances_content" and not engine_session
["force"]:
1663 "lcmOperationType": "terminate",
1664 "nsInstanceId": _id
,
1667 op_id
, _
= self
.engine
.new_item(
1668 rollback
, engine_session
, "nslcmops", nslcmop_desc
, kwargs
1671 outdata
= {"_id": op_id
}
1673 topic
== "netslice_instances_content"
1674 and not engine_session
["force"]
1677 "lcmOperationType": "terminate",
1678 "netsliceInstanceId": _id
,
1681 op_id
, _
= self
.engine
.new_item(
1682 rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None
1685 outdata
= {"_id": op_id
}
1686 # if there is not any deletion in process, delete
1688 op_id
= self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1690 outdata
= {"op_id": op_id
}
1691 cherrypy
.response
.status
= (
1692 HTTPStatus
.ACCEPTED
.value
1694 else HTTPStatus
.NO_CONTENT
.value
1697 elif method
in ("PUT", "PATCH"):
1699 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1701 "Nothing to update. Provide payload and/or query string",
1702 HTTPStatus
.BAD_REQUEST
,
1705 item
in ("nsd_content", "package_content", "nst_content")
1708 completed
= self
.engine
.upload_content(
1714 cherrypy
.request
.headers
,
1717 cherrypy
.response
.headers
["Transaction-Id"] = id
1719 op_id
= self
.engine
.edit_item(
1720 engine_session
, engine_topic
, _id
, indata
, kwargs
1724 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1725 outdata
= {"op_id": op_id
}
1727 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1731 "Method {} not allowed".format(method
),
1732 HTTPStatus
.METHOD_NOT_ALLOWED
,
1735 # if Role information changes, it is needed to reload the information of roles
1736 if topic
== "roles" and method
!= "GET":
1737 self
.authenticator
.load_operation_to_allowed_roles()
1741 and method
== "DELETE"
1742 or topic
in ["users", "roles"]
1743 and method
in ["PUT", "PATCH", "DELETE"]
1745 self
.authenticator
.remove_token_from_cache()
1747 return self
._format
_out
(outdata
, token_info
, _format
)
1748 except Exception as e
:
1762 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1763 http_code_name
= e
.http_code
.name
1764 cherrypy
.log("Exception {}".format(e
))
1767 cherrypy
.response
.status
1768 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1769 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1770 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1771 if hasattr(outdata
, "close"): # is an open file
1775 for rollback_item
in rollback
:
1777 if rollback_item
.get("operation") == "set":
1778 self
.engine
.db
.set_one(
1779 rollback_item
["topic"],
1780 {"_id": rollback_item
["_id"]},
1781 rollback_item
["content"],
1782 fail_on_empty
=False,
1784 elif rollback_item
.get("operation") == "del_list":
1785 self
.engine
.db
.del_list(
1786 rollback_item
["topic"],
1787 rollback_item
["filter"],
1790 self
.engine
.db
.del_one(
1791 rollback_item
["topic"],
1792 {"_id": rollback_item
["_id"]},
1793 fail_on_empty
=False,
1795 except Exception as e2
:
1796 rollback_error_text
= "Rollback Exception {}: {}".format(
1799 cherrypy
.log(rollback_error_text
)
1800 error_text
+= ". " + rollback_error_text
1801 # if isinstance(e, MsgException):
1802 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1803 # engine_topic[:-1], method, error_text)
1805 "code": http_code_name
,
1806 "status": http_code_value
,
1807 "detail": error_text
,
1809 return self
._format
_out
(problem_details
, token_info
)
1810 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1813 self
._format
_login
(token_info
)
1814 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1815 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1816 if outdata
.get(logging_id
):
1817 cherrypy
.request
.login
+= ";{}={}".format(
1818 logging_id
, outdata
[logging_id
][:36]
1822 def _start_service():
1824 Callback function called when cherrypy.engine starts
1825 Override configuration with env variables
1826 Set database, storage, message configuration
1827 Init database with admin/admin user password
1830 global subscription_thread
1831 cherrypy
.log
.error("Starting osm_nbi")
1832 # update general cherrypy configuration
1835 engine_config
= cherrypy
.tree
.apps
["/osm"].config
1836 for k
, v
in environ
.items():
1837 if not k
.startswith("OSMNBI_"):
1839 k1
, _
, k2
= k
[7:].lower().partition("_")
1843 # update static configuration
1844 if k
== "OSMNBI_STATIC_DIR":
1845 engine_config
["/static"]["tools.staticdir.dir"] = v
1846 engine_config
["/static"]["tools.staticdir.on"] = True
1847 elif k
== "OSMNBI_SOCKET_PORT" or k
== "OSMNBI_SERVER_PORT":
1848 update_dict
["server.socket_port"] = int(v
)
1849 elif k
== "OSMNBI_SOCKET_HOST" or k
== "OSMNBI_SERVER_HOST":
1850 update_dict
["server.socket_host"] = v
1851 elif k1
in ("server", "test", "auth", "log"):
1852 update_dict
[k1
+ "." + k2
] = v
1853 elif k1
in ("message", "database", "storage", "authentication"):
1854 # k2 = k2.replace('_', '.')
1855 if k2
in ("port", "db_port"):
1856 engine_config
[k1
][k2
] = int(v
)
1858 engine_config
[k1
][k2
] = v
1860 except ValueError as e
:
1861 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1862 except Exception as e
:
1864 "WARNING: skipping environ '{}' on exception '{}'".format(k
, e
)
1868 cherrypy
.config
.update(update_dict
)
1869 engine_config
["global"].update(update_dict
)
1872 log_format_simple
= (
1873 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1875 log_formatter_simple
= logging
.Formatter(
1876 log_format_simple
, datefmt
="%Y-%m-%dT%H:%M:%S"
1878 logger_server
= logging
.getLogger("cherrypy.error")
1879 logger_access
= logging
.getLogger("cherrypy.access")
1880 logger_cherry
= logging
.getLogger("cherrypy")
1881 logger_nbi
= logging
.getLogger("nbi")
1883 if "log.file" in engine_config
["global"]:
1884 file_handler
= logging
.handlers
.RotatingFileHandler(
1885 engine_config
["global"]["log.file"], maxBytes
=100e6
, backupCount
=9, delay
=0
1887 file_handler
.setFormatter(log_formatter_simple
)
1888 logger_cherry
.addHandler(file_handler
)
1889 logger_nbi
.addHandler(file_handler
)
1890 # log always to standard output
1891 for format_
, logger
in {
1892 "nbi.server %(filename)s:%(lineno)s": logger_server
,
1893 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1894 "%(name)s %(filename)s:%(lineno)s": logger_nbi
,
1896 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1897 log_formatter_cherry
= logging
.Formatter(
1898 log_format_cherry
, datefmt
="%Y-%m-%dT%H:%M:%S"
1900 str_handler
= logging
.StreamHandler()
1901 str_handler
.setFormatter(log_formatter_cherry
)
1902 logger
.addHandler(str_handler
)
1904 if engine_config
["global"].get("log.level"):
1905 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1906 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1908 # logging other modules
1909 for k1
, logname
in {
1910 "message": "nbi.msg",
1911 "database": "nbi.db",
1912 "storage": "nbi.fs",
1914 engine_config
[k1
]["logger_name"] = logname
1915 logger_module
= logging
.getLogger(logname
)
1916 if "logfile" in engine_config
[k1
]:
1917 file_handler
= logging
.handlers
.RotatingFileHandler(
1918 engine_config
[k1
]["logfile"], maxBytes
=100e6
, backupCount
=9, delay
=0
1920 file_handler
.setFormatter(log_formatter_simple
)
1921 logger_module
.addHandler(file_handler
)
1922 if "loglevel" in engine_config
[k1
]:
1923 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1924 # TODO add more entries, e.g.: storage
1925 cherrypy
.tree
.apps
["/osm"].root
.engine
.start(engine_config
)
1926 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.start(engine_config
)
1927 cherrypy
.tree
.apps
["/osm"].root
.engine
.init_db(target_version
=database_version
)
1928 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.init_db(
1929 target_version
=auth_database_version
1932 # start subscriptions thread:
1933 subscription_thread
= SubscriptionThread(
1934 config
=engine_config
, engine
=nbi_server
.engine
1936 subscription_thread
.start()
1937 # Do not capture except SubscriptionException
1939 backend
= engine_config
["authentication"]["backend"]
1941 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1942 nbi_version
, nbi_version_date
, backend
1947 def _stop_service():
1949 Callback function called when cherrypy.engine stops
1950 TODO: Ending database connections.
1952 global subscription_thread
1953 if subscription_thread
:
1954 subscription_thread
.terminate()
1955 subscription_thread
= None
1956 cherrypy
.tree
.apps
["/osm"].root
.engine
.stop()
1957 cherrypy
.log
.error("Stopping osm_nbi")
1960 def nbi(config_file
):
1964 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1965 # 'tools.sessions.on': True,
1966 # 'tools.response_headers.on': True,
1967 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1970 # cherrypy.Server.ssl_module = 'builtin'
1971 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1972 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1973 # cherrypy.Server.thread_pool = 10
1974 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1976 # cherrypy.config.update({'tools.auth_basic.on': True,
1977 # 'tools.auth_basic.realm': 'localhost',
1978 # 'tools.auth_basic.checkpassword': validate_password})
1979 nbi_server
= Server()
1980 cherrypy
.engine
.subscribe("start", _start_service
)
1981 cherrypy
.engine
.subscribe("stop", _stop_service
)
1982 cherrypy
.quickstart(nbi_server
, "/osm", config_file
)
1987 """Usage: {} [options]
1988 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1989 -h|--help: shows this help
1994 # --log-socket-host HOST: send logs to this host")
1995 # --log-socket-port PORT: send logs using this port (default: 9022)")
1998 if __name__
== "__main__":
2000 # load parameters and configuration
2001 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
2002 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
2005 if o
in ("-h", "--help"):
2008 elif o
in ("-c", "--config"):
2010 # elif o == "--log-socket-port":
2011 # log_socket_port = a
2012 # elif o == "--log-socket-host":
2013 # log_socket_host = a
2014 # elif o == "--log-file":
2017 assert False, "Unhandled option"
2019 if not path
.isfile(config_file
):
2021 "configuration file '{}' that not exist".format(config_file
),
2026 for config_file
in (
2027 __file__
[: __file__
.rfind(".")] + ".cfg",
2031 if path
.isfile(config_file
):
2035 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
2040 except getopt
.GetoptError
as e
:
2041 print(str(e
), file=sys
.stderr
)