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:scale:",
441 "METHODS": ("POST",),
442 "ROLE_PERMISSION": "ns_instances:id:terminate:",
445 "METHODS": ("POST",),
446 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
449 "METHODS": ("POST",),
450 "ROLE_PERMISSION": "ns_instances:id:migrate:",
453 "METHODS": ("POST",),
454 "ROLE_PERMISSION": "ns_instances:id:action:",
457 "METHODS": ("POST",),
458 "ROLE_PERMISSION": "ns_instances:id:update:",
464 "ROLE_PERMISSION": "ns_instances:opps:",
467 "ROLE_PERMISSION": "ns_instances:opps:id:",
472 "ROLE_PERMISSION": "vnf_instances:",
473 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
477 "ROLE_PERMISSION": "vnf_instances:",
478 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
481 "METHODS": ("GET", "POST"),
482 "ROLE_PERMISSION": "ns_subscriptions:",
484 "METHODS": ("GET", "DELETE"),
485 "ROLE_PERMISSION": "ns_subscriptions:id:",
492 "vnf_instances": {"METHODS": ("GET", "POST"),
493 "ROLE_PERMISSION": "vnflcm_instances:",
494 "<ID>": {"METHODS": ("GET", "DELETE"),
495 "ROLE_PERMISSION": "vnflcm_instances:id:",
496 "scale": {"METHODS": ("POST",),
497 "ROLE_PERMISSION": "vnflcm_instances:id:scale:"
499 "terminate": {"METHODS": ("POST",),
500 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:"
502 "instantiate": {"METHODS": ("POST",),
503 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:"
507 "vnf_lcm_op_occs": {"METHODS": ("GET",),
508 "ROLE_PERMISSION": "vnf_instances:opps:",
509 "<ID>": {"METHODS": ("GET",),
510 "ROLE_PERMISSION": "vnf_instances:opps:id:"
513 "subscriptions": {"METHODS": ("GET", "POST"),
514 "ROLE_PERMISSION": "vnflcm_subscriptions:",
515 "<ID>": {"METHODS": ("GET", "DELETE"),
516 "ROLE_PERMISSION": "vnflcm_subscriptions:id:"
523 "netslice_templates_content": {
524 "METHODS": ("GET", "POST"),
525 "ROLE_PERMISSION": "slice_templates:",
527 "METHODS": ("GET", "PUT", "DELETE"),
528 "ROLE_PERMISSION": "slice_templates:id:",
531 "netslice_templates": {
532 "METHODS": ("GET", "POST"),
533 "ROLE_PERMISSION": "slice_templates:",
535 "METHODS": ("GET", "DELETE"),
537 "ROLE_PERMISSION": "slice_templates:id:",
539 "METHODS": ("GET", "PUT"),
540 "ROLE_PERMISSION": "slice_templates:id:content:",
543 "METHODS": ("GET",), # descriptor inside package
544 "ROLE_PERMISSION": "slice_templates:id:content:",
548 "ROLE_PERMISSION": "slice_templates:id:content:",
554 "TODO": ("GET", "POST"),
555 "<ID>": {"TODO": ("GET", "DELETE")},
561 "netslice_instances_content": {
562 "METHODS": ("GET", "POST"),
563 "ROLE_PERMISSION": "slice_instances:",
565 "METHODS": ("GET", "DELETE"),
566 "ROLE_PERMISSION": "slice_instances:id:",
569 "netslice_instances": {
570 "METHODS": ("GET", "POST"),
571 "ROLE_PERMISSION": "slice_instances:",
573 "METHODS": ("GET", "DELETE"),
574 "ROLE_PERMISSION": "slice_instances:id:",
576 "METHODS": ("POST",),
577 "ROLE_PERMISSION": "slice_instances:id:terminate:",
580 "METHODS": ("POST",),
581 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
584 "METHODS": ("POST",),
585 "ROLE_PERMISSION": "slice_instances:id:action:",
591 "ROLE_PERMISSION": "slice_instances:opps:",
594 "ROLE_PERMISSION": "slice_instances:opps:id:",
606 "ROLE_PERMISSION": "reports:id:",
615 "alarms": {"METHODS": ("GET", "PATCH"),
616 "ROLE_PERMISSION": "alarms:",
617 "<ID>": {"METHODS": ("GET", "PATCH"),
618 "ROLE_PERMISSION": "alarms:id:",
626 class NbiException(Exception):
627 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
628 Exception.__init
__(self
, message
)
629 self
.http_code
= http_code
632 class Server(object):
634 # to decode bytes to str
635 reader
= getreader("utf-8")
639 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
640 self
.engine
= Engine(self
.authenticator
)
642 def _format_in(self
, kwargs
):
645 if cherrypy
.request
.body
.length
:
646 error_text
= "Invalid input format "
648 if "Content-Type" in cherrypy
.request
.headers
:
649 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
650 error_text
= "Invalid json format "
651 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
652 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
653 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
654 error_text
= "Invalid yaml format "
656 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
658 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
660 "application/binary" in cherrypy
.request
.headers
["Content-Type"]
661 or "application/gzip"
662 in cherrypy
.request
.headers
["Content-Type"]
663 or "application/zip" in cherrypy
.request
.headers
["Content-Type"]
664 or "text/plain" in cherrypy
.request
.headers
["Content-Type"]
666 indata
= cherrypy
.request
.body
# .read()
668 "multipart/form-data"
669 in cherrypy
.request
.headers
["Content-Type"]
671 if "descriptor_file" in kwargs
:
672 filecontent
= kwargs
.pop("descriptor_file")
673 if not filecontent
.file:
675 "empty file or content", HTTPStatus
.BAD_REQUEST
677 indata
= filecontent
.file # .read()
678 if filecontent
.content_type
.value
:
679 cherrypy
.request
.headers
[
681 ] = filecontent
.content_type
.value
683 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
684 # "Only 'Content-Type' of type 'application/json' or
685 # 'application/yaml' for input format are available")
686 error_text
= "Invalid yaml format "
688 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
690 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
692 error_text
= "Invalid yaml format "
693 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
694 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
699 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
702 for k
, v
in kwargs
.items():
703 if isinstance(v
, str):
708 kwargs
[k
] = yaml
.load(v
, Loader
=yaml
.SafeLoader
)
714 or k
.endswith(".gte")
715 or k
.endswith(".lte")
724 elif v
.find(",") > 0:
725 kwargs
[k
] = v
.split(",")
726 elif isinstance(v
, (list, tuple)):
727 for index
in range(0, len(v
)):
732 v
[index
] = yaml
.load(v
[index
], Loader
=yaml
.SafeLoader
)
737 except (ValueError, yaml
.YAMLError
) as exc
:
738 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
739 except KeyError as exc
:
741 "Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
743 except Exception as exc
:
744 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
747 def _format_out(data
, token_info
=None, _format
=None):
749 return string of dictionary data according to requested json, yaml, xml. By default json
750 :param data: response to be sent. Can be a dict, text or file
751 :param token_info: Contains among other username and project
752 :param _format: The format to be set as Content-Type if data is a file
755 accept
= cherrypy
.request
.headers
.get("Accept")
757 if accept
and "text/html" in accept
:
759 data
, cherrypy
.request
, cherrypy
.response
, token_info
761 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
763 elif hasattr(data
, "read"): # file object
765 cherrypy
.response
.headers
["Content-Type"] = _format
766 elif "b" in data
.mode
: # binariy asssumig zip
767 cherrypy
.response
.headers
["Content-Type"] = "application/zip"
769 cherrypy
.response
.headers
["Content-Type"] = "text/plain"
770 # TODO check that cherrypy close file. If not implement pending things to close per thread next
773 if "text/html" in accept
:
775 data
, cherrypy
.request
, cherrypy
.response
, token_info
777 elif "application/yaml" in accept
or "*/*" in accept
:
779 elif "application/json" in accept
or (
780 cherrypy
.response
.status
and cherrypy
.response
.status
>= 300
782 cherrypy
.response
.headers
[
784 ] = "application/json; charset=utf-8"
785 a
= json
.dumps(data
, indent
=4) + "\n"
786 return a
.encode("utf8")
787 cherrypy
.response
.headers
["Content-Type"] = "application/yaml"
788 return yaml
.safe_dump(
792 default_flow_style
=False,
796 ) # , canonical=True, default_style='"'
799 def index(self
, *args
, **kwargs
):
802 if cherrypy
.request
.method
== "GET":
803 token_info
= self
.authenticator
.authorize()
804 outdata
= token_info
# Home page
806 raise cherrypy
.HTTPError(
807 HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
808 "Method {} not allowed for tokens".format(cherrypy
.request
.method
),
811 return self
._format
_out
(outdata
, token_info
)
813 except (EngineException
, AuthException
) as e
:
814 # cherrypy.log("index Exception {}".format(e))
815 cherrypy
.response
.status
= e
.http_code
.value
816 return self
._format
_out
("Welcome to OSM!", token_info
)
819 def version(self
, *args
, **kwargs
):
820 # TODO consider to remove and provide version using the static version file
822 if cherrypy
.request
.method
!= "GET":
824 "Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
828 "Invalid URL or query string for version",
829 HTTPStatus
.METHOD_NOT_ALLOWED
,
831 # TODO include version of other modules, pick up from some kafka admin message
832 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
833 return self
._format
_out
(osm_nbi_version
)
834 except NbiException
as e
:
835 cherrypy
.response
.status
= e
.http_code
.value
837 "code": e
.http_code
.name
,
838 "status": e
.http_code
.value
,
841 return self
._format
_out
(problem_details
, None)
846 "user_domain_name": cherrypy
.tree
.apps
["/osm"]
847 .config
["authentication"]
848 .get("user_domain_name"),
849 "project_domain_name": cherrypy
.tree
.apps
["/osm"]
850 .config
["authentication"]
851 .get("project_domain_name"),
853 return self
._format
_out
(domains
)
854 except NbiException
as e
:
855 cherrypy
.response
.status
= e
.http_code
.value
857 "code": e
.http_code
.name
,
858 "status": e
.http_code
.value
,
861 return self
._format
_out
(problem_details
, None)
864 def _format_login(token_info
):
866 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
868 :param token_info: Dictionary with token content
871 cherrypy
.request
.login
= token_info
.get("username", "-")
872 if token_info
.get("project_name"):
873 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
874 if token_info
.get("id"):
875 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
877 # NS Fault Management
879 def nsfm(self
, version
=None, topic
=None, uuid
=None, project_name
=None, ns_id
=None, *args
, **kwargs
):
880 if topic
== 'alarms':
882 method
= cherrypy
.request
.method
883 role_permission
= self
._check
_valid
_url
_method
(method
, "nsfm", version
, topic
, None, None, *args
)
884 query_string_operations
= self
._extract
_query
_string
_operations
(kwargs
, method
)
886 self
.authenticator
.authorize(role_permission
, query_string_operations
, None)
888 # to handle get request
889 if cherrypy
.request
.method
== 'GET':
890 # if request is on basis of uuid
891 if uuid
and uuid
!= 'None':
893 alarm
= self
.engine
.db
.get_one("alarms", {"uuid": uuid
})
894 alarm_action
= self
.engine
.db
.get_one("alarms_action", {"uuid": uuid
})
895 alarm
.update(alarm_action
)
896 vnf
= self
.engine
.db
.get_one("vnfrs", {"nsr-id-ref": alarm
["tags"]["ns_id"]})
897 alarm
["vnf-id"] = vnf
["_id"]
898 return self
._format
_out
(str(alarm
))
900 return self
._format
_out
("Please provide valid alarm uuid")
901 elif ns_id
and ns_id
!= 'None':
902 # if request is on basis of ns_id
904 alarms
= self
.engine
.db
.get_list("alarms", {"tags.ns_id": ns_id
})
906 alarm_action
= self
.engine
.db
.get_one("alarms_action", {"uuid": alarm
['uuid']})
907 alarm
.update(alarm_action
)
908 return self
._format
_out
(str(alarms
))
910 return self
._format
_out
("Please provide valid ns id")
912 # to return only alarm which are related to given project
913 project
= self
.engine
.db
.get_one("projects", {"name": project_name
})
914 project_id
= project
.get('_id')
915 ns_list
= self
.engine
.db
.get_list("nsrs", {"_admin.projects_read": project_id
})
918 ns_ids
.append(ns
.get("_id"))
919 alarms
= self
.engine
.db
.get_list("alarms")
920 alarm_list
= [alarm
for alarm
in alarms
if alarm
["tags"]["ns_id"] in ns_ids
]
921 for alrm
in alarm_list
:
922 action
= self
.engine
.db
.get_one("alarms_action", {"uuid": alrm
.get("uuid")})
924 return self
._format
_out
(str(alarm_list
))
925 # to handle patch request for alarm update
926 elif cherrypy
.request
.method
== 'PATCH':
927 data
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
929 # check if uuid is valid
930 self
.engine
.db
.get_one("alarms", {"uuid": data
.get("uuid")})
932 return self
._format
_out
("Please provide valid alarm uuid.")
933 if data
.get("is_enable") is not None:
934 if data
.get("is_enable"):
937 alarm_status
= 'disabled'
938 self
.engine
.db
.set_one("alarms", {"uuid": data
.get("uuid")},
939 {"alarm_status": alarm_status
})
941 self
.engine
.db
.set_one("alarms", {"uuid": data
.get("uuid")},
942 {"threshold": data
.get("threshold")})
943 return self
._format
_out
("Alarm updated")
944 except Exception as e
:
945 cherrypy
.response
.status
= e
.http_code
.value
946 if isinstance(e
, (NbiException
, EngineException
, DbException
, FsException
, MsgException
, AuthException
,
947 ValidationError
, AuthconnException
)):
948 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
949 http_code_name
= e
.http_code
.name
950 cherrypy
.log("Exception {}".format(e
))
952 http_code_value
= cherrypy
.response
.status
= HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
953 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
954 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
956 "code": http_code_name
,
957 "status": http_code_value
,
960 return self
._format
_out
(problem_details
)
963 def token(self
, method
, token_id
=None, kwargs
=None):
965 # self.engine.load_dbase(cherrypy.request.app.config)
966 indata
= self
._format
_in
(kwargs
)
967 if not isinstance(indata
, dict):
969 "Expected application/yaml or application/json Content-Type",
970 HTTPStatus
.BAD_REQUEST
,
974 token_info
= self
.authenticator
.authorize()
976 self
._format
_login
(token_info
)
978 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
980 outdata
= self
.authenticator
.get_token_list(token_info
)
981 elif method
== "POST":
983 token_info
= self
.authenticator
.authorize()
987 indata
.update(kwargs
)
988 # This is needed to log the user when authentication fails
989 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
990 outdata
= token_info
= self
.authenticator
.new_token(
991 token_info
, indata
, cherrypy
.request
.remote
993 cherrypy
.session
["Authorization"] = outdata
["_id"]
994 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
996 self
._format
_login
(token_info
)
997 # password expiry check
998 if self
.authenticator
.check_password_expiry(outdata
):
999 outdata
= {"id": outdata
["id"],
1000 "message": "change_password",
1001 "user_id": outdata
["user_id"]
1003 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1004 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1005 elif method
== "DELETE":
1006 if not token_id
and "id" in kwargs
:
1007 token_id
= kwargs
["id"]
1009 token_info
= self
.authenticator
.authorize()
1011 self
._format
_login
(token_info
)
1012 token_id
= token_info
["_id"]
1013 outdata
= self
.authenticator
.del_token(token_id
)
1015 cherrypy
.session
["Authorization"] = "logout"
1016 # cherrypy.response.cookie["Authorization"] = token_id
1017 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1020 "Method {} not allowed for token".format(method
),
1021 HTTPStatus
.METHOD_NOT_ALLOWED
,
1023 return self
._format
_out
(outdata
, token_info
)
1026 def test(self
, *args
, **kwargs
):
1027 if not cherrypy
.config
.get("server.enable_test") or (
1028 isinstance(cherrypy
.config
["server.enable_test"], str)
1029 and cherrypy
.config
["server.enable_test"].lower() == "false"
1031 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
1032 return "test URL is disabled"
1034 if args
and args
[0] == "help":
1036 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1037 "sleep/<time>\nmessage/topic\n</pre></html>"
1040 elif args
and args
[0] == "init":
1042 # self.engine.load_dbase(cherrypy.request.app.config)
1043 self
.engine
.create_admin()
1044 return "Done. User 'admin', password 'admin' created"
1046 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1047 return self
._format
_out
("Database already initialized")
1048 elif args
and args
[0] == "file":
1049 return cherrypy
.lib
.static
.serve_file(
1050 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1],
1054 elif args
and args
[0] == "file2":
1056 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1]
1058 f
= open(f_path
, "r")
1059 cherrypy
.response
.headers
["Content-type"] = "text/plain"
1062 elif len(args
) == 2 and args
[0] == "db-clear":
1063 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
1064 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
1065 elif len(args
) and args
[0] == "fs-clear":
1067 folders
= (args
[1],)
1069 folders
= self
.engine
.fs
.dir_ls(".")
1070 for folder
in folders
:
1071 self
.engine
.fs
.file_delete(folder
)
1072 return ",".join(folders
) + " folders deleted\n"
1073 elif args
and args
[0] == "login":
1074 if not cherrypy
.request
.headers
.get("Authorization"):
1075 cherrypy
.response
.headers
[
1077 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
1078 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1079 elif args
and args
[0] == "login2":
1080 if not cherrypy
.request
.headers
.get("Authorization"):
1081 cherrypy
.response
.headers
[
1083 ] = 'Bearer realm="Access to OSM site"'
1084 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1085 elif args
and args
[0] == "sleep":
1088 sleep_time
= int(args
[1])
1090 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1091 return self
._format
_out
("Database already initialized")
1092 thread_info
= cherrypy
.thread_data
1094 time
.sleep(sleep_time
)
1096 elif len(args
) >= 2 and args
[0] == "message":
1097 main_topic
= args
[1]
1098 return_text
= "<html><pre>{} ->\n".format(main_topic
)
1100 if cherrypy
.request
.method
== "POST":
1101 to_send
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
1102 for k
, v
in to_send
.items():
1103 self
.engine
.msg
.write(main_topic
, k
, v
)
1104 return_text
+= " {}: {}\n".format(k
, v
)
1105 elif cherrypy
.request
.method
== "GET":
1106 for k
, v
in kwargs
.items():
1107 v_dict
= yaml
.load(v
, Loader
=yaml
.SafeLoader
)
1108 self
.engine
.msg
.write(main_topic
, k
, v_dict
)
1109 return_text
+= " {}: {}\n".format(k
, v_dict
)
1110 except Exception as e
:
1111 return_text
+= "Error: " + str(e
)
1112 return_text
+= "</pre></html>\n"
1116 "<html><pre>\nheaders:\n args: {}\n".format(args
)
1117 + " kwargs: {}\n".format(kwargs
)
1118 + " headers: {}\n".format(cherrypy
.request
.headers
)
1119 + " path_info: {}\n".format(cherrypy
.request
.path_info
)
1120 + " query_string: {}\n".format(cherrypy
.request
.query_string
)
1121 + " session: {}\n".format(cherrypy
.session
)
1122 + " cookie: {}\n".format(cherrypy
.request
.cookie
)
1123 + " method: {}\n".format(cherrypy
.request
.method
)
1124 + " session: {}\n".format(cherrypy
.session
.get("fieldname"))
1127 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
1128 if cherrypy
.request
.body
.length
:
1129 return_text
+= " content: {}\n".format(
1131 cherrypy
.request
.body
.read(
1132 int(cherrypy
.request
.headers
.get("Content-Length", 0))
1137 return_text
+= "thread: {}\n".format(thread_info
)
1138 return_text
+= "</pre></html>"
1142 def _check_valid_url_method(method
, *args
):
1145 "URL must contain at least 'main_topic/version/topic'",
1146 HTTPStatus
.METHOD_NOT_ALLOWED
,
1149 reference
= valid_url_methods
1153 if not isinstance(reference
, dict):
1155 "URL contains unexpected extra items '{}'".format(arg
),
1156 HTTPStatus
.METHOD_NOT_ALLOWED
,
1159 if arg
in reference
:
1160 reference
= reference
[arg
]
1161 elif "<ID>" in reference
:
1162 reference
= reference
["<ID>"]
1163 elif "*" in reference
:
1164 # if there is content
1166 reference
= reference
["*"]
1170 "Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
1172 if "TODO" in reference
and method
in reference
["TODO"]:
1174 "Method {} not supported yet for this URL".format(method
),
1175 HTTPStatus
.NOT_IMPLEMENTED
,
1177 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
1179 "Method {} not supported for this URL".format(method
),
1180 HTTPStatus
.METHOD_NOT_ALLOWED
,
1182 return reference
["ROLE_PERMISSION"] + method
.lower()
1185 def _set_location_header(main_topic
, version
, topic
, id):
1187 Insert response header Location with the URL of created item base on URL params
1194 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1195 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(
1196 main_topic
, version
, topic
, id
1201 def _extract_query_string_operations(kwargs
, method
):
1207 query_string_operations
= []
1209 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1210 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
1211 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
1212 return query_string_operations
1215 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
1217 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1218 Check that users has rights to use them and returs the admin_query
1219 :param token_info: token_info rights obtained by token
1220 :param kwargs: query string input.
1221 :param method: http method: GET, POSST, PUT, ...
1223 :return: admin_query dictionary with keys:
1224 public: True, False or None
1225 force: True or False
1226 project_id: tuple with projects used for accessing an element
1227 set_project: tuple with projects that a created element will belong to
1228 method: show, list, delete, write
1232 "project_id": (token_info
["project_id"],),
1233 "username": token_info
["username"],
1234 "admin": token_info
["admin"],
1236 "allow_show_user_project_role": token_info
["allow_show_user_project_role"],
1240 if "FORCE" in kwargs
:
1242 kwargs
["FORCE"].lower() != "false"
1243 ): # if None or True set force to True
1244 admin_query
["force"] = True
1247 if "PUBLIC" in kwargs
:
1249 kwargs
["PUBLIC"].lower() != "false"
1250 ): # if None or True set public to True
1251 admin_query
["public"] = True
1253 admin_query
["public"] = False
1254 del kwargs
["PUBLIC"]
1256 if "ADMIN" in kwargs
:
1257 behave_as
= kwargs
.pop("ADMIN")
1258 if behave_as
.lower() != "false":
1259 if not token_info
["admin"]:
1261 "Only admin projects can use 'ADMIN' query string",
1262 HTTPStatus
.UNAUTHORIZED
,
1265 not behave_as
or behave_as
.lower() == "true"
1266 ): # convert True, None to empty list
1267 admin_query
["project_id"] = ()
1268 elif isinstance(behave_as
, (list, tuple)):
1269 admin_query
["project_id"] = behave_as
1270 else: # isinstance(behave_as, str)
1271 admin_query
["project_id"] = (behave_as
,)
1272 if "SET_PROJECT" in kwargs
:
1273 set_project
= kwargs
.pop("SET_PROJECT")
1275 admin_query
["set_project"] = list(admin_query
["project_id"])
1277 if isinstance(set_project
, str):
1278 set_project
= (set_project
,)
1279 if admin_query
["project_id"]:
1280 for p
in set_project
:
1281 if p
not in admin_query
["project_id"]:
1283 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1284 "'ADMIN='{p}'".format(p
=p
),
1285 HTTPStatus
.UNAUTHORIZED
,
1287 admin_query
["set_project"] = set_project
1290 # if "PROJECT_READ" in kwargs:
1291 # admin_query["project"] = kwargs.pop("project")
1292 # if admin_query["project"] == token_info["project_id"]:
1295 admin_query
["method"] = "show"
1297 admin_query
["method"] = "list"
1298 elif method
== "DELETE":
1299 admin_query
["method"] = "delete"
1301 admin_query
["method"] = "write"
1321 engine_session
= None
1323 if not main_topic
or not version
or not topic
:
1325 "URL must contain at least 'main_topic/version/topic'",
1326 HTTPStatus
.METHOD_NOT_ALLOWED
,
1328 if main_topic
not in (
1340 "URL main_topic '{}' not supported".format(main_topic
),
1341 HTTPStatus
.METHOD_NOT_ALLOWED
,
1345 "URL version '{}' not supported".format(version
),
1346 HTTPStatus
.METHOD_NOT_ALLOWED
,
1351 and "METHOD" in kwargs
1352 and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1354 method
= kwargs
.pop("METHOD")
1356 method
= cherrypy
.request
.method
1358 role_permission
= self
._check
_valid
_url
_method
(
1359 method
, main_topic
, version
, topic
, _id
, item
, *args
1361 query_string_operations
= self
._extract
_query
_string
_operations
(
1364 if main_topic
== "admin" and topic
== "tokens":
1365 return self
.token(method
, _id
, kwargs
)
1366 token_info
= self
.authenticator
.authorize(
1367 role_permission
, query_string_operations
, _id
1369 if main_topic
== "admin" and topic
== "domains":
1370 return self
.domain()
1371 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
1372 indata
= self
._format
_in
(kwargs
)
1373 engine_topic
= topic
1375 if item
and topic
!= "pm_jobs":
1378 if main_topic
== "nsd":
1379 engine_topic
= "nsds"
1380 elif main_topic
== "vnfpkgm":
1381 engine_topic
= "vnfds"
1382 if topic
== "vnfpkg_op_occs":
1383 engine_topic
= "vnfpkgops"
1384 if topic
== "vnf_packages" and item
== "action":
1385 engine_topic
= "vnfpkgops"
1386 elif main_topic
== "nslcm":
1387 engine_topic
= "nsrs"
1388 if topic
== "ns_lcm_op_occs":
1389 engine_topic
= "nslcmops"
1390 if topic
== "vnfrs" or topic
== "vnf_instances":
1391 engine_topic
= "vnfrs"
1392 elif main_topic
== "vnflcm":
1393 if topic
== "vnf_lcm_op_occs":
1394 engine_topic
= "vnflcmops"
1395 elif main_topic
== "nst":
1396 engine_topic
= "nsts"
1397 elif main_topic
== "nsilcm":
1398 engine_topic
= "nsis"
1399 if topic
== "nsi_lcm_op_occs":
1400 engine_topic
= "nsilcmops"
1401 elif main_topic
== "pdu":
1402 engine_topic
= "pdus"
1404 engine_topic
== "vims"
1405 ): # TODO this is for backward compatibility, it will be removed in the future
1406 engine_topic
= "vim_accounts"
1408 if topic
== "subscriptions":
1409 engine_topic
= main_topic
+ "_" + topic
1421 if item
in ("vnfd", "nsd", "nst"):
1422 path
= "$DESCRIPTOR"
1425 elif item
== "artifacts":
1429 file, _format
= self
.engine
.get_file(
1434 cherrypy
.request
.headers
.get("Accept"),
1438 outdata
= self
.engine
.get_item_list(
1439 engine_session
, engine_topic
, kwargs
, api_req
=True
1442 if item
== "reports":
1443 # TODO check that project_id (_id in this context) has permissions
1446 if "vcaStatusRefresh" in kwargs
:
1447 filter_q
= {"vcaStatusRefresh": kwargs
["vcaStatusRefresh"]}
1448 outdata
= self
.engine
.get_item(engine_session
, engine_topic
, _id
, filter_q
, True)
1450 elif method
== "POST":
1451 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1453 "ns_descriptors_content",
1454 "vnf_packages_content",
1455 "netslice_templates_content",
1457 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1459 _id
, _
= self
.engine
.new_item(
1465 cherrypy
.request
.headers
,
1467 completed
= self
.engine
.upload_content(
1473 cherrypy
.request
.headers
,
1476 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1478 cherrypy
.response
.headers
["Transaction-Id"] = _id
1479 outdata
= {"id": _id
}
1480 elif topic
== "ns_instances_content":
1482 _id
, _
= self
.engine
.new_item(
1483 rollback
, engine_session
, engine_topic
, indata
, kwargs
1486 indata
["lcmOperationType"] = "instantiate"
1487 indata
["nsInstanceId"] = _id
1488 nslcmop_id
, _
= self
.engine
.new_item(
1489 rollback
, engine_session
, "nslcmops", indata
, None
1491 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1492 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1493 elif topic
== "ns_instances" and item
:
1494 indata
["lcmOperationType"] = item
1495 indata
["nsInstanceId"] = _id
1496 _id
, _
= self
.engine
.new_item(
1497 rollback
, engine_session
, "nslcmops", indata
, kwargs
1499 self
._set
_location
_header
(
1500 main_topic
, version
, "ns_lcm_op_occs", _id
1502 outdata
= {"id": _id
}
1503 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1504 elif topic
== "netslice_instances_content":
1505 # creates NetSlice_Instance_record (NSIR)
1506 _id
, _
= self
.engine
.new_item(
1507 rollback
, engine_session
, engine_topic
, indata
, kwargs
1509 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1510 indata
["lcmOperationType"] = "instantiate"
1511 indata
["netsliceInstanceId"] = _id
1512 nsilcmop_id
, _
= self
.engine
.new_item(
1513 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1515 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1516 elif topic
== "netslice_instances" and item
:
1517 indata
["lcmOperationType"] = item
1518 indata
["netsliceInstanceId"] = _id
1519 _id
, _
= self
.engine
.new_item(
1520 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1522 self
._set
_location
_header
(
1523 main_topic
, version
, "nsi_lcm_op_occs", _id
1525 outdata
= {"id": _id
}
1526 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1527 elif topic
== "vnf_packages" and item
== "action":
1528 indata
["lcmOperationType"] = item
1529 indata
["vnfPkgId"] = _id
1530 _id
, _
= self
.engine
.new_item(
1531 rollback
, engine_session
, "vnfpkgops", indata
, kwargs
1533 self
._set
_location
_header
(
1534 main_topic
, version
, "vnfpkg_op_occs", _id
1536 outdata
= {"id": _id
}
1537 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1538 elif topic
== "subscriptions":
1539 _id
, _
= self
.engine
.new_item(
1540 rollback
, engine_session
, engine_topic
, indata
, kwargs
1542 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1544 link
["self"] = cherrypy
.response
.headers
["Location"]
1547 "filter": indata
["filter"],
1548 "callbackUri": indata
["CallbackUri"],
1551 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1552 elif topic
== "vnf_instances" and item
:
1553 indata
["lcmOperationType"] = item
1554 indata
["vnfInstanceId"] = _id
1555 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, "vnflcmops", indata
, kwargs
)
1556 self
._set
_location
_header
(main_topic
, version
, "vnf_lcm_op_occs", _id
)
1557 outdata
= {"id": _id
}
1558 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1560 _id
, op_id
= self
.engine
.new_item(
1566 cherrypy
.request
.headers
,
1568 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1569 outdata
= {"id": _id
}
1571 outdata
["op_id"] = op_id
1572 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1573 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1575 elif method
== "DELETE":
1577 outdata
= self
.engine
.del_item_list(
1578 engine_session
, engine_topic
, kwargs
1580 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1581 else: # len(args) > 1
1582 # for NS NSI generate an operation
1584 if topic
== "ns_instances_content" and not engine_session
["force"]:
1586 "lcmOperationType": "terminate",
1587 "nsInstanceId": _id
,
1590 op_id
, _
= self
.engine
.new_item(
1591 rollback
, engine_session
, "nslcmops", nslcmop_desc
, kwargs
1594 outdata
= {"_id": op_id
}
1596 topic
== "netslice_instances_content"
1597 and not engine_session
["force"]
1600 "lcmOperationType": "terminate",
1601 "netsliceInstanceId": _id
,
1604 op_id
, _
= self
.engine
.new_item(
1605 rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None
1608 outdata
= {"_id": op_id
}
1609 # if there is not any deletion in process, delete
1611 op_id
= self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1613 outdata
= {"op_id": op_id
}
1614 cherrypy
.response
.status
= (
1615 HTTPStatus
.ACCEPTED
.value
1617 else HTTPStatus
.NO_CONTENT
.value
1620 elif method
in ("PUT", "PATCH"):
1622 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1624 "Nothing to update. Provide payload and/or query string",
1625 HTTPStatus
.BAD_REQUEST
,
1628 item
in ("nsd_content", "package_content", "nst_content")
1631 completed
= self
.engine
.upload_content(
1637 cherrypy
.request
.headers
,
1640 cherrypy
.response
.headers
["Transaction-Id"] = id
1642 op_id
= self
.engine
.edit_item(
1643 engine_session
, engine_topic
, _id
, indata
, kwargs
1647 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1648 outdata
= {"op_id": op_id
}
1650 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1654 "Method {} not allowed".format(method
),
1655 HTTPStatus
.METHOD_NOT_ALLOWED
,
1658 # if Role information changes, it is needed to reload the information of roles
1659 if topic
== "roles" and method
!= "GET":
1660 self
.authenticator
.load_operation_to_allowed_roles()
1664 and method
== "DELETE"
1665 or topic
in ["users", "roles"]
1666 and method
in ["PUT", "PATCH", "DELETE"]
1668 self
.authenticator
.remove_token_from_cache()
1670 return self
._format
_out
(outdata
, token_info
, _format
)
1671 except Exception as e
:
1685 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1686 http_code_name
= e
.http_code
.name
1687 cherrypy
.log("Exception {}".format(e
))
1690 cherrypy
.response
.status
1691 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1692 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1693 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1694 if hasattr(outdata
, "close"): # is an open file
1698 for rollback_item
in rollback
:
1700 if rollback_item
.get("operation") == "set":
1701 self
.engine
.db
.set_one(
1702 rollback_item
["topic"],
1703 {"_id": rollback_item
["_id"]},
1704 rollback_item
["content"],
1705 fail_on_empty
=False,
1707 elif rollback_item
.get("operation") == "del_list":
1708 self
.engine
.db
.del_list(
1709 rollback_item
["topic"],
1710 rollback_item
["filter"],
1711 fail_on_empty
=False,
1714 self
.engine
.db
.del_one(
1715 rollback_item
["topic"],
1716 {"_id": rollback_item
["_id"]},
1717 fail_on_empty
=False,
1719 except Exception as e2
:
1720 rollback_error_text
= "Rollback Exception {}: {}".format(
1723 cherrypy
.log(rollback_error_text
)
1724 error_text
+= ". " + rollback_error_text
1725 # if isinstance(e, MsgException):
1726 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1727 # engine_topic[:-1], method, error_text)
1729 "code": http_code_name
,
1730 "status": http_code_value
,
1731 "detail": error_text
,
1733 return self
._format
_out
(problem_details
, token_info
)
1734 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1737 self
._format
_login
(token_info
)
1738 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1739 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1740 if outdata
.get(logging_id
):
1741 cherrypy
.request
.login
+= ";{}={}".format(
1742 logging_id
, outdata
[logging_id
][:36]
1746 def _start_service():
1748 Callback function called when cherrypy.engine starts
1749 Override configuration with env variables
1750 Set database, storage, message configuration
1751 Init database with admin/admin user password
1754 global subscription_thread
1755 cherrypy
.log
.error("Starting osm_nbi")
1756 # update general cherrypy configuration
1759 engine_config
= cherrypy
.tree
.apps
["/osm"].config
1760 for k
, v
in environ
.items():
1761 if not k
.startswith("OSMNBI_"):
1763 k1
, _
, k2
= k
[7:].lower().partition("_")
1767 # update static configuration
1768 if k
== "OSMNBI_STATIC_DIR":
1769 engine_config
["/static"]["tools.staticdir.dir"] = v
1770 engine_config
["/static"]["tools.staticdir.on"] = True
1771 elif k
== "OSMNBI_SOCKET_PORT" or k
== "OSMNBI_SERVER_PORT":
1772 update_dict
["server.socket_port"] = int(v
)
1773 elif k
== "OSMNBI_SOCKET_HOST" or k
== "OSMNBI_SERVER_HOST":
1774 update_dict
["server.socket_host"] = v
1775 elif k1
in ("server", "test", "auth", "log"):
1776 update_dict
[k1
+ "." + k2
] = v
1777 elif k1
in ("message", "database", "storage", "authentication"):
1778 # k2 = k2.replace('_', '.')
1779 if k2
in ("port", "db_port"):
1780 engine_config
[k1
][k2
] = int(v
)
1782 engine_config
[k1
][k2
] = v
1784 except ValueError as e
:
1785 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1786 except Exception as e
:
1787 cherrypy
.log
.warn("skipping environ '{}' on exception '{}'".format(k
, e
))
1790 cherrypy
.config
.update(update_dict
)
1791 engine_config
["global"].update(update_dict
)
1794 log_format_simple
= (
1795 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1797 log_formatter_simple
= logging
.Formatter(
1798 log_format_simple
, datefmt
="%Y-%m-%dT%H:%M:%S"
1800 logger_server
= logging
.getLogger("cherrypy.error")
1801 logger_access
= logging
.getLogger("cherrypy.access")
1802 logger_cherry
= logging
.getLogger("cherrypy")
1803 logger_nbi
= logging
.getLogger("nbi")
1805 if "log.file" in engine_config
["global"]:
1806 file_handler
= logging
.handlers
.RotatingFileHandler(
1807 engine_config
["global"]["log.file"], maxBytes
=100e6
, backupCount
=9, delay
=0
1809 file_handler
.setFormatter(log_formatter_simple
)
1810 logger_cherry
.addHandler(file_handler
)
1811 logger_nbi
.addHandler(file_handler
)
1812 # log always to standard output
1813 for format_
, logger
in {
1814 "nbi.server %(filename)s:%(lineno)s": logger_server
,
1815 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1816 "%(name)s %(filename)s:%(lineno)s": logger_nbi
,
1818 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1819 log_formatter_cherry
= logging
.Formatter(
1820 log_format_cherry
, datefmt
="%Y-%m-%dT%H:%M:%S"
1822 str_handler
= logging
.StreamHandler()
1823 str_handler
.setFormatter(log_formatter_cherry
)
1824 logger
.addHandler(str_handler
)
1826 if engine_config
["global"].get("log.level"):
1827 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1828 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1830 # logging other modules
1831 for k1
, logname
in {
1832 "message": "nbi.msg",
1833 "database": "nbi.db",
1834 "storage": "nbi.fs",
1836 engine_config
[k1
]["logger_name"] = logname
1837 logger_module
= logging
.getLogger(logname
)
1838 if "logfile" in engine_config
[k1
]:
1839 file_handler
= logging
.handlers
.RotatingFileHandler(
1840 engine_config
[k1
]["logfile"], maxBytes
=100e6
, backupCount
=9, delay
=0
1842 file_handler
.setFormatter(log_formatter_simple
)
1843 logger_module
.addHandler(file_handler
)
1844 if "loglevel" in engine_config
[k1
]:
1845 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1846 # TODO add more entries, e.g.: storage
1847 cherrypy
.tree
.apps
["/osm"].root
.engine
.start(engine_config
)
1848 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.start(engine_config
)
1849 cherrypy
.tree
.apps
["/osm"].root
.engine
.init_db(target_version
=database_version
)
1850 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.init_db(
1851 target_version
=auth_database_version
1854 # start subscriptions thread:
1855 subscription_thread
= SubscriptionThread(
1856 config
=engine_config
, engine
=nbi_server
.engine
1858 subscription_thread
.start()
1859 # Do not capture except SubscriptionException
1861 backend
= engine_config
["authentication"]["backend"]
1863 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1864 nbi_version
, nbi_version_date
, backend
1869 def _stop_service():
1871 Callback function called when cherrypy.engine stops
1872 TODO: Ending database connections.
1874 global subscription_thread
1875 if subscription_thread
:
1876 subscription_thread
.terminate()
1877 subscription_thread
= None
1878 cherrypy
.tree
.apps
["/osm"].root
.engine
.stop()
1879 cherrypy
.log
.error("Stopping osm_nbi")
1882 def nbi(config_file
):
1886 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1887 # 'tools.sessions.on': True,
1888 # 'tools.response_headers.on': True,
1889 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1892 # cherrypy.Server.ssl_module = 'builtin'
1893 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1894 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1895 # cherrypy.Server.thread_pool = 10
1896 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1898 # cherrypy.config.update({'tools.auth_basic.on': True,
1899 # 'tools.auth_basic.realm': 'localhost',
1900 # 'tools.auth_basic.checkpassword': validate_password})
1901 nbi_server
= Server()
1902 cherrypy
.engine
.subscribe("start", _start_service
)
1903 cherrypy
.engine
.subscribe("stop", _stop_service
)
1904 cherrypy
.quickstart(nbi_server
, "/osm", config_file
)
1909 """Usage: {} [options]
1910 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1911 -h|--help: shows this help
1916 # --log-socket-host HOST: send logs to this host")
1917 # --log-socket-port PORT: send logs using this port (default: 9022)")
1920 if __name__
== "__main__":
1922 # load parameters and configuration
1923 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
1924 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1927 if o
in ("-h", "--help"):
1930 elif o
in ("-c", "--config"):
1932 # elif o == "--log-socket-port":
1933 # log_socket_port = a
1934 # elif o == "--log-socket-host":
1935 # log_socket_host = a
1936 # elif o == "--log-file":
1939 assert False, "Unhandled option"
1941 if not path
.isfile(config_file
):
1943 "configuration file '{}' that not exist".format(config_file
),
1948 for config_file
in (
1949 __file__
[: __file__
.rfind(".")] + ".cfg",
1953 if path
.isfile(config_file
):
1957 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
1962 except getopt
.GetoptError
as e
:
1963 print(str(e
), file=sys
.stderr
)