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:",
500 "vnf_instances": {"METHODS": ("GET", "POST"),
501 "ROLE_PERMISSION": "vnflcm_instances:",
502 "<ID>": {"METHODS": ("GET", "DELETE"),
503 "ROLE_PERMISSION": "vnflcm_instances:id:",
504 "scale": {"METHODS": ("POST",),
505 "ROLE_PERMISSION": "vnflcm_instances:id:scale:"
507 "terminate": {"METHODS": ("POST",),
508 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:"
510 "instantiate": {"METHODS": ("POST",),
511 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:"
515 "vnf_lcm_op_occs": {"METHODS": ("GET",),
516 "ROLE_PERMISSION": "vnf_instances:opps:",
517 "<ID>": {"METHODS": ("GET",),
518 "ROLE_PERMISSION": "vnf_instances:opps:id:"
521 "subscriptions": {"METHODS": ("GET", "POST"),
522 "ROLE_PERMISSION": "vnflcm_subscriptions:",
523 "<ID>": {"METHODS": ("GET", "DELETE"),
524 "ROLE_PERMISSION": "vnflcm_subscriptions:id:"
531 "netslice_templates_content": {
532 "METHODS": ("GET", "POST"),
533 "ROLE_PERMISSION": "slice_templates:",
535 "METHODS": ("GET", "PUT", "DELETE"),
536 "ROLE_PERMISSION": "slice_templates:id:",
539 "netslice_templates": {
540 "METHODS": ("GET", "POST"),
541 "ROLE_PERMISSION": "slice_templates:",
543 "METHODS": ("GET", "DELETE"),
545 "ROLE_PERMISSION": "slice_templates:id:",
547 "METHODS": ("GET", "PUT"),
548 "ROLE_PERMISSION": "slice_templates:id:content:",
551 "METHODS": ("GET",), # descriptor inside package
552 "ROLE_PERMISSION": "slice_templates:id:content:",
556 "ROLE_PERMISSION": "slice_templates:id:content:",
562 "TODO": ("GET", "POST"),
563 "<ID>": {"TODO": ("GET", "DELETE")},
569 "netslice_instances_content": {
570 "METHODS": ("GET", "POST"),
571 "ROLE_PERMISSION": "slice_instances:",
573 "METHODS": ("GET", "DELETE"),
574 "ROLE_PERMISSION": "slice_instances:id:",
577 "netslice_instances": {
578 "METHODS": ("GET", "POST"),
579 "ROLE_PERMISSION": "slice_instances:",
581 "METHODS": ("GET", "DELETE"),
582 "ROLE_PERMISSION": "slice_instances:id:",
584 "METHODS": ("POST",),
585 "ROLE_PERMISSION": "slice_instances:id:terminate:",
588 "METHODS": ("POST",),
589 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
592 "METHODS": ("POST",),
593 "ROLE_PERMISSION": "slice_instances:id:action:",
599 "ROLE_PERMISSION": "slice_instances:opps:",
602 "ROLE_PERMISSION": "slice_instances:opps:id:",
614 "ROLE_PERMISSION": "reports:id:",
623 "alarms": {"METHODS": ("GET", "PATCH"),
624 "ROLE_PERMISSION": "alarms:",
625 "<ID>": {"METHODS": ("GET", "PATCH"),
626 "ROLE_PERMISSION": "alarms:id:",
634 class NbiException(Exception):
635 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
636 Exception.__init
__(self
, message
)
637 self
.http_code
= http_code
640 class Server(object):
642 # to decode bytes to str
643 reader
= getreader("utf-8")
647 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
648 self
.engine
= Engine(self
.authenticator
)
650 def _format_in(self
, kwargs
):
653 if cherrypy
.request
.body
.length
:
654 error_text
= "Invalid input format "
656 if "Content-Type" in cherrypy
.request
.headers
:
657 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
658 error_text
= "Invalid json format "
659 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
660 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
661 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
662 error_text
= "Invalid yaml format "
664 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
666 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
668 "application/binary" in cherrypy
.request
.headers
["Content-Type"]
669 or "application/gzip"
670 in cherrypy
.request
.headers
["Content-Type"]
671 or "application/zip" in cherrypy
.request
.headers
["Content-Type"]
672 or "text/plain" in cherrypy
.request
.headers
["Content-Type"]
674 indata
= cherrypy
.request
.body
# .read()
676 "multipart/form-data"
677 in cherrypy
.request
.headers
["Content-Type"]
679 if "descriptor_file" in kwargs
:
680 filecontent
= kwargs
.pop("descriptor_file")
681 if not filecontent
.file:
683 "empty file or content", HTTPStatus
.BAD_REQUEST
685 indata
= filecontent
.file # .read()
686 if filecontent
.content_type
.value
:
687 cherrypy
.request
.headers
[
689 ] = filecontent
.content_type
.value
691 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
692 # "Only 'Content-Type' of type 'application/json' or
693 # 'application/yaml' for input format are available")
694 error_text
= "Invalid yaml format "
696 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
698 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
700 error_text
= "Invalid yaml format "
701 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
702 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
707 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
710 for k
, v
in kwargs
.items():
711 if isinstance(v
, str):
716 kwargs
[k
] = yaml
.load(v
, Loader
=yaml
.SafeLoader
)
722 or k
.endswith(".gte")
723 or k
.endswith(".lte")
732 elif v
.find(",") > 0:
733 kwargs
[k
] = v
.split(",")
734 elif isinstance(v
, (list, tuple)):
735 for index
in range(0, len(v
)):
740 v
[index
] = yaml
.load(v
[index
], Loader
=yaml
.SafeLoader
)
745 except (ValueError, yaml
.YAMLError
) as exc
:
746 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
747 except KeyError as exc
:
749 "Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
751 except Exception as exc
:
752 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
755 def _format_out(data
, token_info
=None, _format
=None):
757 return string of dictionary data according to requested json, yaml, xml. By default json
758 :param data: response to be sent. Can be a dict, text or file
759 :param token_info: Contains among other username and project
760 :param _format: The format to be set as Content-Type if data is a file
763 accept
= cherrypy
.request
.headers
.get("Accept")
765 if accept
and "text/html" in accept
:
767 data
, cherrypy
.request
, cherrypy
.response
, token_info
769 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
771 elif hasattr(data
, "read"): # file object
773 cherrypy
.response
.headers
["Content-Type"] = _format
774 elif "b" in data
.mode
: # binariy asssumig zip
775 cherrypy
.response
.headers
["Content-Type"] = "application/zip"
777 cherrypy
.response
.headers
["Content-Type"] = "text/plain"
778 # TODO check that cherrypy close file. If not implement pending things to close per thread next
781 if "text/html" in accept
:
783 data
, cherrypy
.request
, cherrypy
.response
, token_info
785 elif "application/yaml" in accept
or "*/*" in accept
:
787 elif "application/json" in accept
or (
788 cherrypy
.response
.status
and cherrypy
.response
.status
>= 300
790 cherrypy
.response
.headers
[
792 ] = "application/json; charset=utf-8"
793 a
= json
.dumps(data
, indent
=4) + "\n"
794 return a
.encode("utf8")
795 cherrypy
.response
.headers
["Content-Type"] = "application/yaml"
796 return yaml
.safe_dump(
800 default_flow_style
=False,
804 ) # , canonical=True, default_style='"'
807 def index(self
, *args
, **kwargs
):
810 if cherrypy
.request
.method
== "GET":
811 token_info
= self
.authenticator
.authorize()
812 outdata
= token_info
# Home page
814 raise cherrypy
.HTTPError(
815 HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
816 "Method {} not allowed for tokens".format(cherrypy
.request
.method
),
819 return self
._format
_out
(outdata
, token_info
)
821 except (EngineException
, AuthException
) as e
:
822 # cherrypy.log("index Exception {}".format(e))
823 cherrypy
.response
.status
= e
.http_code
.value
824 return self
._format
_out
("Welcome to OSM!", token_info
)
827 def version(self
, *args
, **kwargs
):
828 # TODO consider to remove and provide version using the static version file
830 if cherrypy
.request
.method
!= "GET":
832 "Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
836 "Invalid URL or query string for version",
837 HTTPStatus
.METHOD_NOT_ALLOWED
,
839 # TODO include version of other modules, pick up from some kafka admin message
840 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
841 return self
._format
_out
(osm_nbi_version
)
842 except NbiException
as e
:
843 cherrypy
.response
.status
= e
.http_code
.value
845 "code": e
.http_code
.name
,
846 "status": e
.http_code
.value
,
849 return self
._format
_out
(problem_details
, None)
854 "user_domain_name": cherrypy
.tree
.apps
["/osm"]
855 .config
["authentication"]
856 .get("user_domain_name"),
857 "project_domain_name": cherrypy
.tree
.apps
["/osm"]
858 .config
["authentication"]
859 .get("project_domain_name"),
861 return self
._format
_out
(domains
)
862 except NbiException
as e
:
863 cherrypy
.response
.status
= e
.http_code
.value
865 "code": e
.http_code
.name
,
866 "status": e
.http_code
.value
,
869 return self
._format
_out
(problem_details
, None)
872 def _format_login(token_info
):
874 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
876 :param token_info: Dictionary with token content
879 cherrypy
.request
.login
= token_info
.get("username", "-")
880 if token_info
.get("project_name"):
881 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
882 if token_info
.get("id"):
883 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
885 # NS Fault Management
887 def nsfm(self
, version
=None, topic
=None, uuid
=None, project_name
=None, ns_id
=None, *args
, **kwargs
):
888 if topic
== 'alarms':
890 method
= cherrypy
.request
.method
891 role_permission
= self
._check
_valid
_url
_method
(method
, "nsfm", version
, topic
, None, None, *args
)
892 query_string_operations
= self
._extract
_query
_string
_operations
(kwargs
, method
)
894 self
.authenticator
.authorize(role_permission
, query_string_operations
, None)
896 # to handle get request
897 if cherrypy
.request
.method
== 'GET':
898 # if request is on basis of uuid
899 if uuid
and uuid
!= 'None':
901 alarm
= self
.engine
.db
.get_one("alarms", {"uuid": uuid
})
902 alarm_action
= self
.engine
.db
.get_one("alarms_action", {"uuid": uuid
})
903 alarm
.update(alarm_action
)
904 vnf
= self
.engine
.db
.get_one("vnfrs", {"nsr-id-ref": alarm
["tags"]["ns_id"]})
905 alarm
["vnf-id"] = vnf
["_id"]
906 return self
._format
_out
(str(alarm
))
908 return self
._format
_out
("Please provide valid alarm uuid")
909 elif ns_id
and ns_id
!= 'None':
910 # if request is on basis of ns_id
912 alarms
= self
.engine
.db
.get_list("alarms", {"tags.ns_id": ns_id
})
914 alarm_action
= self
.engine
.db
.get_one("alarms_action", {"uuid": alarm
['uuid']})
915 alarm
.update(alarm_action
)
916 return self
._format
_out
(str(alarms
))
918 return self
._format
_out
("Please provide valid ns id")
920 # to return only alarm which are related to given project
921 project
= self
.engine
.db
.get_one("projects", {"name": project_name
})
922 project_id
= project
.get('_id')
923 ns_list
= self
.engine
.db
.get_list("nsrs", {"_admin.projects_read": project_id
})
926 ns_ids
.append(ns
.get("_id"))
927 alarms
= self
.engine
.db
.get_list("alarms")
928 alarm_list
= [alarm
for alarm
in alarms
if alarm
["tags"]["ns_id"] in ns_ids
]
929 for alrm
in alarm_list
:
930 action
= self
.engine
.db
.get_one("alarms_action", {"uuid": alrm
.get("uuid")})
932 return self
._format
_out
(str(alarm_list
))
933 # to handle patch request for alarm update
934 elif cherrypy
.request
.method
== 'PATCH':
935 data
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
937 # check if uuid is valid
938 self
.engine
.db
.get_one("alarms", {"uuid": data
.get("uuid")})
940 return self
._format
_out
("Please provide valid alarm uuid.")
941 if data
.get("is_enable") is not None:
942 if data
.get("is_enable"):
945 alarm_status
= 'disabled'
946 self
.engine
.db
.set_one("alarms", {"uuid": data
.get("uuid")},
947 {"alarm_status": alarm_status
})
949 self
.engine
.db
.set_one("alarms", {"uuid": data
.get("uuid")},
950 {"threshold": data
.get("threshold")})
951 return self
._format
_out
("Alarm updated")
952 except Exception as e
:
953 cherrypy
.response
.status
= e
.http_code
.value
954 if isinstance(e
, (NbiException
, EngineException
, DbException
, FsException
, MsgException
, AuthException
,
955 ValidationError
, AuthconnException
)):
956 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
957 http_code_name
= e
.http_code
.name
958 cherrypy
.log("Exception {}".format(e
))
960 http_code_value
= cherrypy
.response
.status
= HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
961 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
962 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
964 "code": http_code_name
,
965 "status": http_code_value
,
968 return self
._format
_out
(problem_details
)
971 def token(self
, method
, token_id
=None, kwargs
=None):
973 # self.engine.load_dbase(cherrypy.request.app.config)
974 indata
= self
._format
_in
(kwargs
)
975 if not isinstance(indata
, dict):
977 "Expected application/yaml or application/json Content-Type",
978 HTTPStatus
.BAD_REQUEST
,
982 token_info
= self
.authenticator
.authorize()
984 self
._format
_login
(token_info
)
986 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
988 outdata
= self
.authenticator
.get_token_list(token_info
)
989 elif method
== "POST":
991 token_info
= self
.authenticator
.authorize()
995 indata
.update(kwargs
)
996 # This is needed to log the user when authentication fails
997 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
998 outdata
= token_info
= self
.authenticator
.new_token(
999 token_info
, indata
, cherrypy
.request
.remote
1001 cherrypy
.session
["Authorization"] = outdata
["_id"]
1002 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
1004 self
._format
_login
(token_info
)
1005 # password expiry check
1006 if self
.authenticator
.check_password_expiry(outdata
):
1007 outdata
= {"id": outdata
["id"],
1008 "message": "change_password",
1009 "user_id": outdata
["user_id"]
1011 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1012 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1013 elif method
== "DELETE":
1014 if not token_id
and "id" in kwargs
:
1015 token_id
= kwargs
["id"]
1017 token_info
= self
.authenticator
.authorize()
1019 self
._format
_login
(token_info
)
1020 token_id
= token_info
["_id"]
1021 outdata
= self
.authenticator
.del_token(token_id
)
1023 cherrypy
.session
["Authorization"] = "logout"
1024 # cherrypy.response.cookie["Authorization"] = token_id
1025 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1028 "Method {} not allowed for token".format(method
),
1029 HTTPStatus
.METHOD_NOT_ALLOWED
,
1031 return self
._format
_out
(outdata
, token_info
)
1034 def test(self
, *args
, **kwargs
):
1035 if not cherrypy
.config
.get("server.enable_test") or (
1036 isinstance(cherrypy
.config
["server.enable_test"], str)
1037 and cherrypy
.config
["server.enable_test"].lower() == "false"
1039 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
1040 return "test URL is disabled"
1042 if args
and args
[0] == "help":
1044 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1045 "sleep/<time>\nmessage/topic\n</pre></html>"
1048 elif args
and args
[0] == "init":
1050 # self.engine.load_dbase(cherrypy.request.app.config)
1051 self
.engine
.create_admin()
1052 return "Done. User 'admin', password 'admin' created"
1054 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1055 return self
._format
_out
("Database already initialized")
1056 elif args
and args
[0] == "file":
1057 return cherrypy
.lib
.static
.serve_file(
1058 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1],
1062 elif args
and args
[0] == "file2":
1064 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1]
1066 f
= open(f_path
, "r")
1067 cherrypy
.response
.headers
["Content-type"] = "text/plain"
1070 elif len(args
) == 2 and args
[0] == "db-clear":
1071 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
1072 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
1073 elif len(args
) and args
[0] == "fs-clear":
1075 folders
= (args
[1],)
1077 folders
= self
.engine
.fs
.dir_ls(".")
1078 for folder
in folders
:
1079 self
.engine
.fs
.file_delete(folder
)
1080 return ",".join(folders
) + " folders deleted\n"
1081 elif args
and args
[0] == "login":
1082 if not cherrypy
.request
.headers
.get("Authorization"):
1083 cherrypy
.response
.headers
[
1085 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
1086 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1087 elif args
and args
[0] == "login2":
1088 if not cherrypy
.request
.headers
.get("Authorization"):
1089 cherrypy
.response
.headers
[
1091 ] = 'Bearer realm="Access to OSM site"'
1092 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1093 elif args
and args
[0] == "sleep":
1096 sleep_time
= int(args
[1])
1098 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1099 return self
._format
_out
("Database already initialized")
1100 thread_info
= cherrypy
.thread_data
1102 time
.sleep(sleep_time
)
1104 elif len(args
) >= 2 and args
[0] == "message":
1105 main_topic
= args
[1]
1106 return_text
= "<html><pre>{} ->\n".format(main_topic
)
1108 if cherrypy
.request
.method
== "POST":
1109 to_send
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
1110 for k
, v
in to_send
.items():
1111 self
.engine
.msg
.write(main_topic
, k
, v
)
1112 return_text
+= " {}: {}\n".format(k
, v
)
1113 elif cherrypy
.request
.method
== "GET":
1114 for k
, v
in kwargs
.items():
1115 v_dict
= yaml
.load(v
, Loader
=yaml
.SafeLoader
)
1116 self
.engine
.msg
.write(main_topic
, k
, v_dict
)
1117 return_text
+= " {}: {}\n".format(k
, v_dict
)
1118 except Exception as e
:
1119 return_text
+= "Error: " + str(e
)
1120 return_text
+= "</pre></html>\n"
1124 "<html><pre>\nheaders:\n args: {}\n".format(args
)
1125 + " kwargs: {}\n".format(kwargs
)
1126 + " headers: {}\n".format(cherrypy
.request
.headers
)
1127 + " path_info: {}\n".format(cherrypy
.request
.path_info
)
1128 + " query_string: {}\n".format(cherrypy
.request
.query_string
)
1129 + " session: {}\n".format(cherrypy
.session
)
1130 + " cookie: {}\n".format(cherrypy
.request
.cookie
)
1131 + " method: {}\n".format(cherrypy
.request
.method
)
1132 + " session: {}\n".format(cherrypy
.session
.get("fieldname"))
1135 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
1136 if cherrypy
.request
.body
.length
:
1137 return_text
+= " content: {}\n".format(
1139 cherrypy
.request
.body
.read(
1140 int(cherrypy
.request
.headers
.get("Content-Length", 0))
1145 return_text
+= "thread: {}\n".format(thread_info
)
1146 return_text
+= "</pre></html>"
1150 def _check_valid_url_method(method
, *args
):
1153 "URL must contain at least 'main_topic/version/topic'",
1154 HTTPStatus
.METHOD_NOT_ALLOWED
,
1157 reference
= valid_url_methods
1161 if not isinstance(reference
, dict):
1163 "URL contains unexpected extra items '{}'".format(arg
),
1164 HTTPStatus
.METHOD_NOT_ALLOWED
,
1167 if arg
in reference
:
1168 reference
= reference
[arg
]
1169 elif "<ID>" in reference
:
1170 reference
= reference
["<ID>"]
1171 elif "*" in reference
:
1172 # if there is content
1174 reference
= reference
["*"]
1178 "Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
1180 if "TODO" in reference
and method
in reference
["TODO"]:
1182 "Method {} not supported yet for this URL".format(method
),
1183 HTTPStatus
.NOT_IMPLEMENTED
,
1185 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
1187 "Method {} not supported for this URL".format(method
),
1188 HTTPStatus
.METHOD_NOT_ALLOWED
,
1190 return reference
["ROLE_PERMISSION"] + method
.lower()
1193 def _set_location_header(main_topic
, version
, topic
, id):
1195 Insert response header Location with the URL of created item base on URL params
1202 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1203 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(
1204 main_topic
, version
, topic
, id
1209 def _extract_query_string_operations(kwargs
, method
):
1215 query_string_operations
= []
1217 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1218 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
1219 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
1220 return query_string_operations
1223 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
1225 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1226 Check that users has rights to use them and returs the admin_query
1227 :param token_info: token_info rights obtained by token
1228 :param kwargs: query string input.
1229 :param method: http method: GET, POSST, PUT, ...
1231 :return: admin_query dictionary with keys:
1232 public: True, False or None
1233 force: True or False
1234 project_id: tuple with projects used for accessing an element
1235 set_project: tuple with projects that a created element will belong to
1236 method: show, list, delete, write
1240 "project_id": (token_info
["project_id"],),
1241 "username": token_info
["username"],
1242 "admin": token_info
["admin"],
1244 "allow_show_user_project_role": token_info
["allow_show_user_project_role"],
1248 if "FORCE" in kwargs
:
1250 kwargs
["FORCE"].lower() != "false"
1251 ): # if None or True set force to True
1252 admin_query
["force"] = True
1255 if "PUBLIC" in kwargs
:
1257 kwargs
["PUBLIC"].lower() != "false"
1258 ): # if None or True set public to True
1259 admin_query
["public"] = True
1261 admin_query
["public"] = False
1262 del kwargs
["PUBLIC"]
1264 if "ADMIN" in kwargs
:
1265 behave_as
= kwargs
.pop("ADMIN")
1266 if behave_as
.lower() != "false":
1267 if not token_info
["admin"]:
1269 "Only admin projects can use 'ADMIN' query string",
1270 HTTPStatus
.UNAUTHORIZED
,
1273 not behave_as
or behave_as
.lower() == "true"
1274 ): # convert True, None to empty list
1275 admin_query
["project_id"] = ()
1276 elif isinstance(behave_as
, (list, tuple)):
1277 admin_query
["project_id"] = behave_as
1278 else: # isinstance(behave_as, str)
1279 admin_query
["project_id"] = (behave_as
,)
1280 if "SET_PROJECT" in kwargs
:
1281 set_project
= kwargs
.pop("SET_PROJECT")
1283 admin_query
["set_project"] = list(admin_query
["project_id"])
1285 if isinstance(set_project
, str):
1286 set_project
= (set_project
,)
1287 if admin_query
["project_id"]:
1288 for p
in set_project
:
1289 if p
not in admin_query
["project_id"]:
1291 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1292 "'ADMIN='{p}'".format(p
=p
),
1293 HTTPStatus
.UNAUTHORIZED
,
1295 admin_query
["set_project"] = set_project
1298 # if "PROJECT_READ" in kwargs:
1299 # admin_query["project"] = kwargs.pop("project")
1300 # if admin_query["project"] == token_info["project_id"]:
1303 admin_query
["method"] = "show"
1305 admin_query
["method"] = "list"
1306 elif method
== "DELETE":
1307 admin_query
["method"] = "delete"
1309 admin_query
["method"] = "write"
1329 engine_session
= None
1331 if not main_topic
or not version
or not topic
:
1333 "URL must contain at least 'main_topic/version/topic'",
1334 HTTPStatus
.METHOD_NOT_ALLOWED
,
1336 if main_topic
not in (
1348 "URL main_topic '{}' not supported".format(main_topic
),
1349 HTTPStatus
.METHOD_NOT_ALLOWED
,
1353 "URL version '{}' not supported".format(version
),
1354 HTTPStatus
.METHOD_NOT_ALLOWED
,
1359 and "METHOD" in kwargs
1360 and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1362 method
= kwargs
.pop("METHOD")
1364 method
= cherrypy
.request
.method
1366 role_permission
= self
._check
_valid
_url
_method
(
1367 method
, main_topic
, version
, topic
, _id
, item
, *args
1369 query_string_operations
= self
._extract
_query
_string
_operations
(
1372 if main_topic
== "admin" and topic
== "tokens":
1373 return self
.token(method
, _id
, kwargs
)
1374 token_info
= self
.authenticator
.authorize(
1375 role_permission
, query_string_operations
, _id
1377 if main_topic
== "admin" and topic
== "domains":
1378 return self
.domain()
1379 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
1380 indata
= self
._format
_in
(kwargs
)
1381 engine_topic
= topic
1383 if item
and topic
!= "pm_jobs":
1386 if main_topic
== "nsd":
1387 engine_topic
= "nsds"
1388 elif main_topic
== "vnfpkgm":
1389 engine_topic
= "vnfds"
1390 if topic
== "vnfpkg_op_occs":
1391 engine_topic
= "vnfpkgops"
1392 if topic
== "vnf_packages" and item
== "action":
1393 engine_topic
= "vnfpkgops"
1394 elif main_topic
== "nslcm":
1395 engine_topic
= "nsrs"
1396 if topic
== "ns_lcm_op_occs":
1397 engine_topic
= "nslcmops"
1398 if topic
== "vnfrs" or topic
== "vnf_instances":
1399 engine_topic
= "vnfrs"
1400 elif main_topic
== "vnflcm":
1401 if topic
== "vnf_lcm_op_occs":
1402 engine_topic
= "vnflcmops"
1403 elif main_topic
== "nst":
1404 engine_topic
= "nsts"
1405 elif main_topic
== "nsilcm":
1406 engine_topic
= "nsis"
1407 if topic
== "nsi_lcm_op_occs":
1408 engine_topic
= "nsilcmops"
1409 elif main_topic
== "pdu":
1410 engine_topic
= "pdus"
1412 engine_topic
== "vims"
1413 ): # TODO this is for backward compatibility, it will be removed in the future
1414 engine_topic
= "vim_accounts"
1416 if topic
== "subscriptions":
1417 engine_topic
= main_topic
+ "_" + topic
1429 if item
in ("vnfd", "nsd", "nst"):
1430 path
= "$DESCRIPTOR"
1433 elif item
== "artifacts":
1437 file, _format
= self
.engine
.get_file(
1442 cherrypy
.request
.headers
.get("Accept"),
1446 outdata
= self
.engine
.get_item_list(
1447 engine_session
, engine_topic
, kwargs
, api_req
=True
1450 if item
== "reports":
1451 # TODO check that project_id (_id in this context) has permissions
1454 if "vcaStatusRefresh" in kwargs
:
1455 filter_q
= {"vcaStatusRefresh": kwargs
["vcaStatusRefresh"]}
1456 outdata
= self
.engine
.get_item(engine_session
, engine_topic
, _id
, filter_q
, True)
1458 elif method
== "POST":
1459 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1461 "ns_descriptors_content",
1462 "vnf_packages_content",
1463 "netslice_templates_content",
1465 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1467 _id
, _
= self
.engine
.new_item(
1473 cherrypy
.request
.headers
,
1475 completed
= self
.engine
.upload_content(
1481 cherrypy
.request
.headers
,
1484 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1486 cherrypy
.response
.headers
["Transaction-Id"] = _id
1487 outdata
= {"id": _id
}
1488 elif topic
== "ns_instances_content":
1490 _id
, _
= self
.engine
.new_item(
1491 rollback
, engine_session
, engine_topic
, indata
, kwargs
1494 indata
["lcmOperationType"] = "instantiate"
1495 indata
["nsInstanceId"] = _id
1496 nslcmop_id
, _
= self
.engine
.new_item(
1497 rollback
, engine_session
, "nslcmops", indata
, None
1499 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1500 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1501 elif topic
== "ns_instances" and item
:
1502 indata
["lcmOperationType"] = item
1503 indata
["nsInstanceId"] = _id
1504 _id
, _
= self
.engine
.new_item(
1505 rollback
, engine_session
, "nslcmops", indata
, kwargs
1507 self
._set
_location
_header
(
1508 main_topic
, version
, "ns_lcm_op_occs", _id
1510 outdata
= {"id": _id
}
1511 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1512 elif topic
== "netslice_instances_content":
1513 # creates NetSlice_Instance_record (NSIR)
1514 _id
, _
= self
.engine
.new_item(
1515 rollback
, engine_session
, engine_topic
, indata
, kwargs
1517 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1518 indata
["lcmOperationType"] = "instantiate"
1519 indata
["netsliceInstanceId"] = _id
1520 nsilcmop_id
, _
= self
.engine
.new_item(
1521 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1523 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1524 elif topic
== "netslice_instances" and item
:
1525 indata
["lcmOperationType"] = item
1526 indata
["netsliceInstanceId"] = _id
1527 _id
, _
= self
.engine
.new_item(
1528 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1530 self
._set
_location
_header
(
1531 main_topic
, version
, "nsi_lcm_op_occs", _id
1533 outdata
= {"id": _id
}
1534 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1535 elif topic
== "vnf_packages" and item
== "action":
1536 indata
["lcmOperationType"] = item
1537 indata
["vnfPkgId"] = _id
1538 _id
, _
= self
.engine
.new_item(
1539 rollback
, engine_session
, "vnfpkgops", indata
, kwargs
1541 self
._set
_location
_header
(
1542 main_topic
, version
, "vnfpkg_op_occs", _id
1544 outdata
= {"id": _id
}
1545 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1546 elif topic
== "subscriptions":
1547 _id
, _
= self
.engine
.new_item(
1548 rollback
, engine_session
, engine_topic
, indata
, kwargs
1550 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1552 link
["self"] = cherrypy
.response
.headers
["Location"]
1555 "filter": indata
["filter"],
1556 "callbackUri": indata
["CallbackUri"],
1559 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1560 elif topic
== "vnf_instances" and item
:
1561 indata
["lcmOperationType"] = item
1562 indata
["vnfInstanceId"] = _id
1563 _id
, _
= self
.engine
.new_item(rollback
, engine_session
, "vnflcmops", indata
, kwargs
)
1564 self
._set
_location
_header
(main_topic
, version
, "vnf_lcm_op_occs", _id
)
1565 outdata
= {"id": _id
}
1566 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1568 _id
, op_id
= self
.engine
.new_item(
1574 cherrypy
.request
.headers
,
1576 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1577 outdata
= {"id": _id
}
1579 outdata
["op_id"] = op_id
1580 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1581 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1583 elif method
== "DELETE":
1585 outdata
= self
.engine
.del_item_list(
1586 engine_session
, engine_topic
, kwargs
1588 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1589 else: # len(args) > 1
1590 # for NS NSI generate an operation
1592 if topic
== "ns_instances_content" and not engine_session
["force"]:
1594 "lcmOperationType": "terminate",
1595 "nsInstanceId": _id
,
1598 op_id
, _
= self
.engine
.new_item(
1599 rollback
, engine_session
, "nslcmops", nslcmop_desc
, kwargs
1602 outdata
= {"_id": op_id
}
1604 topic
== "netslice_instances_content"
1605 and not engine_session
["force"]
1608 "lcmOperationType": "terminate",
1609 "netsliceInstanceId": _id
,
1612 op_id
, _
= self
.engine
.new_item(
1613 rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None
1616 outdata
= {"_id": op_id
}
1617 # if there is not any deletion in process, delete
1619 op_id
= self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1621 outdata
= {"op_id": op_id
}
1622 cherrypy
.response
.status
= (
1623 HTTPStatus
.ACCEPTED
.value
1625 else HTTPStatus
.NO_CONTENT
.value
1628 elif method
in ("PUT", "PATCH"):
1630 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1632 "Nothing to update. Provide payload and/or query string",
1633 HTTPStatus
.BAD_REQUEST
,
1636 item
in ("nsd_content", "package_content", "nst_content")
1639 completed
= self
.engine
.upload_content(
1645 cherrypy
.request
.headers
,
1648 cherrypy
.response
.headers
["Transaction-Id"] = id
1650 op_id
= self
.engine
.edit_item(
1651 engine_session
, engine_topic
, _id
, indata
, kwargs
1655 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1656 outdata
= {"op_id": op_id
}
1658 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1662 "Method {} not allowed".format(method
),
1663 HTTPStatus
.METHOD_NOT_ALLOWED
,
1666 # if Role information changes, it is needed to reload the information of roles
1667 if topic
== "roles" and method
!= "GET":
1668 self
.authenticator
.load_operation_to_allowed_roles()
1672 and method
== "DELETE"
1673 or topic
in ["users", "roles"]
1674 and method
in ["PUT", "PATCH", "DELETE"]
1676 self
.authenticator
.remove_token_from_cache()
1678 return self
._format
_out
(outdata
, token_info
, _format
)
1679 except Exception as e
:
1693 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1694 http_code_name
= e
.http_code
.name
1695 cherrypy
.log("Exception {}".format(e
))
1698 cherrypy
.response
.status
1699 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1700 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1701 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1702 if hasattr(outdata
, "close"): # is an open file
1706 for rollback_item
in rollback
:
1708 if rollback_item
.get("operation") == "set":
1709 self
.engine
.db
.set_one(
1710 rollback_item
["topic"],
1711 {"_id": rollback_item
["_id"]},
1712 rollback_item
["content"],
1713 fail_on_empty
=False,
1715 elif rollback_item
.get("operation") == "del_list":
1716 self
.engine
.db
.del_list(
1717 rollback_item
["topic"],
1718 rollback_item
["filter"],
1719 fail_on_empty
=False,
1722 self
.engine
.db
.del_one(
1723 rollback_item
["topic"],
1724 {"_id": rollback_item
["_id"]},
1725 fail_on_empty
=False,
1727 except Exception as e2
:
1728 rollback_error_text
= "Rollback Exception {}: {}".format(
1731 cherrypy
.log(rollback_error_text
)
1732 error_text
+= ". " + rollback_error_text
1733 # if isinstance(e, MsgException):
1734 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1735 # engine_topic[:-1], method, error_text)
1737 "code": http_code_name
,
1738 "status": http_code_value
,
1739 "detail": error_text
,
1741 return self
._format
_out
(problem_details
, token_info
)
1742 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1745 self
._format
_login
(token_info
)
1746 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1747 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1748 if outdata
.get(logging_id
):
1749 cherrypy
.request
.login
+= ";{}={}".format(
1750 logging_id
, outdata
[logging_id
][:36]
1754 def _start_service():
1756 Callback function called when cherrypy.engine starts
1757 Override configuration with env variables
1758 Set database, storage, message configuration
1759 Init database with admin/admin user password
1762 global subscription_thread
1763 cherrypy
.log
.error("Starting osm_nbi")
1764 # update general cherrypy configuration
1767 engine_config
= cherrypy
.tree
.apps
["/osm"].config
1768 for k
, v
in environ
.items():
1769 if not k
.startswith("OSMNBI_"):
1771 k1
, _
, k2
= k
[7:].lower().partition("_")
1775 # update static configuration
1776 if k
== "OSMNBI_STATIC_DIR":
1777 engine_config
["/static"]["tools.staticdir.dir"] = v
1778 engine_config
["/static"]["tools.staticdir.on"] = True
1779 elif k
== "OSMNBI_SOCKET_PORT" or k
== "OSMNBI_SERVER_PORT":
1780 update_dict
["server.socket_port"] = int(v
)
1781 elif k
== "OSMNBI_SOCKET_HOST" or k
== "OSMNBI_SERVER_HOST":
1782 update_dict
["server.socket_host"] = v
1783 elif k1
in ("server", "test", "auth", "log"):
1784 update_dict
[k1
+ "." + k2
] = v
1785 elif k1
in ("message", "database", "storage", "authentication"):
1786 # k2 = k2.replace('_', '.')
1787 if k2
in ("port", "db_port"):
1788 engine_config
[k1
][k2
] = int(v
)
1790 engine_config
[k1
][k2
] = v
1792 except ValueError as e
:
1793 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1794 except Exception as e
:
1795 cherrypy
.log
.warn("skipping environ '{}' on exception '{}'".format(k
, e
))
1798 cherrypy
.config
.update(update_dict
)
1799 engine_config
["global"].update(update_dict
)
1802 log_format_simple
= (
1803 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1805 log_formatter_simple
= logging
.Formatter(
1806 log_format_simple
, datefmt
="%Y-%m-%dT%H:%M:%S"
1808 logger_server
= logging
.getLogger("cherrypy.error")
1809 logger_access
= logging
.getLogger("cherrypy.access")
1810 logger_cherry
= logging
.getLogger("cherrypy")
1811 logger_nbi
= logging
.getLogger("nbi")
1813 if "log.file" in engine_config
["global"]:
1814 file_handler
= logging
.handlers
.RotatingFileHandler(
1815 engine_config
["global"]["log.file"], maxBytes
=100e6
, backupCount
=9, delay
=0
1817 file_handler
.setFormatter(log_formatter_simple
)
1818 logger_cherry
.addHandler(file_handler
)
1819 logger_nbi
.addHandler(file_handler
)
1820 # log always to standard output
1821 for format_
, logger
in {
1822 "nbi.server %(filename)s:%(lineno)s": logger_server
,
1823 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1824 "%(name)s %(filename)s:%(lineno)s": logger_nbi
,
1826 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1827 log_formatter_cherry
= logging
.Formatter(
1828 log_format_cherry
, datefmt
="%Y-%m-%dT%H:%M:%S"
1830 str_handler
= logging
.StreamHandler()
1831 str_handler
.setFormatter(log_formatter_cherry
)
1832 logger
.addHandler(str_handler
)
1834 if engine_config
["global"].get("log.level"):
1835 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1836 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1838 # logging other modules
1839 for k1
, logname
in {
1840 "message": "nbi.msg",
1841 "database": "nbi.db",
1842 "storage": "nbi.fs",
1844 engine_config
[k1
]["logger_name"] = logname
1845 logger_module
= logging
.getLogger(logname
)
1846 if "logfile" in engine_config
[k1
]:
1847 file_handler
= logging
.handlers
.RotatingFileHandler(
1848 engine_config
[k1
]["logfile"], maxBytes
=100e6
, backupCount
=9, delay
=0
1850 file_handler
.setFormatter(log_formatter_simple
)
1851 logger_module
.addHandler(file_handler
)
1852 if "loglevel" in engine_config
[k1
]:
1853 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1854 # TODO add more entries, e.g.: storage
1855 cherrypy
.tree
.apps
["/osm"].root
.engine
.start(engine_config
)
1856 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.start(engine_config
)
1857 cherrypy
.tree
.apps
["/osm"].root
.engine
.init_db(target_version
=database_version
)
1858 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.init_db(
1859 target_version
=auth_database_version
1862 # start subscriptions thread:
1863 subscription_thread
= SubscriptionThread(
1864 config
=engine_config
, engine
=nbi_server
.engine
1866 subscription_thread
.start()
1867 # Do not capture except SubscriptionException
1869 backend
= engine_config
["authentication"]["backend"]
1871 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1872 nbi_version
, nbi_version_date
, backend
1877 def _stop_service():
1879 Callback function called when cherrypy.engine stops
1880 TODO: Ending database connections.
1882 global subscription_thread
1883 if subscription_thread
:
1884 subscription_thread
.terminate()
1885 subscription_thread
= None
1886 cherrypy
.tree
.apps
["/osm"].root
.engine
.stop()
1887 cherrypy
.log
.error("Stopping osm_nbi")
1890 def nbi(config_file
):
1894 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1895 # 'tools.sessions.on': True,
1896 # 'tools.response_headers.on': True,
1897 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1900 # cherrypy.Server.ssl_module = 'builtin'
1901 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1902 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1903 # cherrypy.Server.thread_pool = 10
1904 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1906 # cherrypy.config.update({'tools.auth_basic.on': True,
1907 # 'tools.auth_basic.realm': 'localhost',
1908 # 'tools.auth_basic.checkpassword': validate_password})
1909 nbi_server
= Server()
1910 cherrypy
.engine
.subscribe("start", _start_service
)
1911 cherrypy
.engine
.subscribe("stop", _stop_service
)
1912 cherrypy
.quickstart(nbi_server
, "/osm", config_file
)
1917 """Usage: {} [options]
1918 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1919 -h|--help: shows this help
1924 # --log-socket-host HOST: send logs to this host")
1925 # --log-socket-port PORT: send logs using this port (default: 9022)")
1928 if __name__
== "__main__":
1930 # load parameters and configuration
1931 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
1932 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1935 if o
in ("-h", "--help"):
1938 elif o
in ("-c", "--config"):
1940 # elif o == "--log-socket-port":
1941 # log_socket_port = a
1942 # elif o == "--log-socket-host":
1943 # log_socket_host = a
1944 # elif o == "--log-file":
1947 assert False, "Unhandled option"
1949 if not path
.isfile(config_file
):
1951 "configuration file '{}' that not exist".format(config_file
),
1956 for config_file
in (
1957 __file__
[: __file__
.rfind(".")] + ".cfg",
1961 if path
.isfile(config_file
):
1965 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
1970 except getopt
.GetoptError
as e
:
1971 print(str(e
), file=sys
.stderr
)