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
.utils
import cef_event
, cef_event_builder
32 from osm_nbi
.validation
import ValidationError
33 from osm_common
.dbbase
import DbException
34 from osm_common
.fsbase
import FsException
35 from osm_common
.msgbase
import MsgException
36 from http
import HTTPStatus
37 from codecs
import getreader
38 from os
import environ
, path
39 from osm_nbi
import version
as nbi_version
, version_date
as nbi_version_date
41 __author__
= "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
43 __version__
= "0.1.3" # file version, not NBI version
44 version_date
= "Aug 2019"
46 database_version
= "1.2"
47 auth_database_version
= "1.0"
48 nbi_server
= None # instance of Server class
49 subscription_thread
= None # instance of SubscriptionThread class
53 North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
54 URL: /osm GET POST PUT DELETE PATCH
56 /ns_descriptors_content O O
62 /artifacts[/<artifactPath>] O
70 /vnf_packages_content O O
74 /package_content O5 O5
77 /artifacts[/<artifactPath>] O5
82 /ns_instances_content O O
96 /vnf_instances (also vnfrs for compatibility) O
112 /vim_accounts (also vims for compatibility) O O
126 /netslice_templates_content O O
128 /netslice_templates O O
132 /artifacts[/<artifactPath>] O
134 /<subscriptionId> X X
137 /netslice_instances_content O O
138 /<SliceInstanceId> O O
139 /netslice_instances O O
140 /<SliceInstanceId> O O
145 /<nsiLcmOpOccId> O O O
147 /<subscriptionId> X X
150 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
151 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
152 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
153 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
155 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
156 item of the array, that is, pass if any item of the array pass the filter.
157 It allows both ne and neq for not equal
158 TODO: 4.3.3 Attribute selectors
159 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
160 (none) … same as “exclude_default”
161 all_fields … all attributes.
162 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
163 conditionally mandatory, and that are not provided in <list>.
164 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
165 are not conditionally mandatory, and that are provided in <list>.
166 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
167 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
168 the particular resource
169 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
170 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
171 present specification for the particular resource, but that are not part of <list>
172 Additionally it admits some administrator values:
173 FORCE: To force operations skipping dependency checkings
174 ADMIN: To act as an administrator or a different project
175 PUBLIC: To get public descriptors or set a descriptor as public
176 SET_PROJECT: To make a descriptor available for other project
178 Header field name Reference Example Descriptions
179 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
180 This header field shall be present if the response is expected to have a non-empty message body.
181 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
182 This header field shall be present if the request has a non-empty message body.
183 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
184 Details are specified in clause 4.5.3.
185 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
186 Header field name Reference Example Descriptions
187 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
188 This header field shall be present if the response has a non-empty message body.
189 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
190 new resource has been created.
191 This header field shall be present if the response status code is 201 or 3xx.
192 In the present document this header field is also used if the response status code is 202 and a new resource was
194 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
195 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
197 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
199 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
200 response, and the total length of the file.
201 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
204 valid_query_string
= ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
205 # ^ Contains possible administrative query string words:
206 # ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
207 # (not owned by my session project).
208 # PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
209 # FORCE=True(by default)|False: Force edition/deletion operations
210 # SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
212 valid_url_methods
= {
213 # contains allowed URL and methods, and the role_permission name
217 "METHODS": ("GET", "POST", "DELETE"),
218 "ROLE_PERMISSION": "tokens:",
219 "<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"},
222 "METHODS": ("GET", "POST"),
223 "ROLE_PERMISSION": "users:",
225 "METHODS": ("GET", "DELETE", "PATCH"),
226 "ROLE_PERMISSION": "users:id:",
230 "METHODS": ("GET", "POST"),
231 "ROLE_PERMISSION": "projects:",
233 "METHODS": ("GET", "DELETE", "PATCH"),
234 "ROLE_PERMISSION": "projects:id:",
238 "METHODS": ("GET", "POST"),
239 "ROLE_PERMISSION": "roles:",
241 "METHODS": ("GET", "DELETE", "PATCH"),
242 "ROLE_PERMISSION": "roles:id:",
246 "METHODS": ("GET", "POST"),
247 "ROLE_PERMISSION": "vims:",
249 "METHODS": ("GET", "DELETE", "PATCH"),
250 "ROLE_PERMISSION": "vims:id:",
254 "METHODS": ("GET", "POST"),
255 "ROLE_PERMISSION": "vim_accounts:",
257 "METHODS": ("GET", "DELETE", "PATCH"),
258 "ROLE_PERMISSION": "vim_accounts:id:",
262 "METHODS": ("GET", "POST"),
263 "ROLE_PERMISSION": "wim_accounts:",
265 "METHODS": ("GET", "DELETE", "PATCH"),
266 "ROLE_PERMISSION": "wim_accounts:id:",
270 "METHODS": ("GET", "POST"),
271 "ROLE_PERMISSION": "sdn_controllers:",
273 "METHODS": ("GET", "DELETE", "PATCH"),
274 "ROLE_PERMISSION": "sdn_controllers:id:",
278 "METHODS": ("GET", "POST"),
279 "ROLE_PERMISSION": "k8sclusters:",
281 "METHODS": ("GET", "DELETE", "PATCH"),
282 "ROLE_PERMISSION": "k8sclusters:id:",
286 "METHODS": ("GET", "POST"),
287 "ROLE_PERMISSION": "vca:",
289 "METHODS": ("GET", "DELETE", "PATCH"),
290 "ROLE_PERMISSION": "vca:id:",
294 "METHODS": ("GET", "POST"),
295 "ROLE_PERMISSION": "k8srepos:",
297 "METHODS": ("GET", "DELETE"),
298 "ROLE_PERMISSION": "k8srepos:id:",
302 "METHODS": ("GET", "POST"),
303 "ROLE_PERMISSION": "osmrepos:",
305 "METHODS": ("GET", "DELETE", "PATCH"),
306 "ROLE_PERMISSION": "osmrepos:id:",
311 "ROLE_PERMISSION": "domains:",
318 "METHODS": ("GET", "POST"),
319 "ROLE_PERMISSION": "pduds:",
321 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
322 "ROLE_PERMISSION": "pduds:id:",
329 "ns_descriptors_content": {
330 "METHODS": ("GET", "POST"),
331 "ROLE_PERMISSION": "nsds:",
333 "METHODS": ("GET", "PUT", "DELETE"),
334 "ROLE_PERMISSION": "nsds:id:",
338 "METHODS": ("GET", "POST"),
339 "ROLE_PERMISSION": "nsds:",
341 "METHODS": ("GET", "DELETE", "PATCH"),
342 "ROLE_PERMISSION": "nsds:id:",
344 "METHODS": ("GET", "PUT"),
345 "ROLE_PERMISSION": "nsds:id:content:",
348 "METHODS": ("GET",), # descriptor inside package
349 "ROLE_PERMISSION": "nsds:id:content:",
353 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
359 "TODO": ("GET", "POST"),
361 "TODO": ("GET", "DELETE", "PATCH"),
362 "pnfd_content": {"TODO": ("GET", "PUT")},
366 "TODO": ("GET", "POST"),
367 "<ID>": {"TODO": ("GET", "DELETE")},
373 "vnf_packages_content": {
374 "METHODS": ("GET", "POST"),
375 "ROLE_PERMISSION": "vnfds:",
377 "METHODS": ("GET", "PUT", "DELETE"),
378 "ROLE_PERMISSION": "vnfds:id:",
382 "METHODS": ("GET", "POST"),
383 "ROLE_PERMISSION": "vnfds:",
385 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
386 "ROLE_PERMISSION": "vnfds:id:",
388 "METHODS": ("GET", "PUT"), # package
389 "ROLE_PERMISSION": "vnfds:id:",
393 "ROLE_PERMISSION": "vnfds:id:upload:",
397 "METHODS": ("GET",), # descriptor inside package
398 "ROLE_PERMISSION": "vnfds:id:content:",
402 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
406 "METHODS": ("POST",),
407 "ROLE_PERMISSION": "vnfds:id:action:",
412 "TODO": ("GET", "POST"),
413 "<ID>": {"TODO": ("GET", "DELETE")},
417 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
418 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"},
424 "ns_instances_content": {
425 "METHODS": ("GET", "POST"),
426 "ROLE_PERMISSION": "ns_instances:",
428 "METHODS": ("GET", "DELETE"),
429 "ROLE_PERMISSION": "ns_instances:id:",
433 "METHODS": ("GET", "POST"),
434 "ROLE_PERMISSION": "ns_instances:",
436 "METHODS": ("GET", "DELETE"),
437 "ROLE_PERMISSION": "ns_instances:id:",
439 "METHODS": ("POST",),
440 "ROLE_PERMISSION": "ns_instances:id:heal:",
443 "METHODS": ("POST",),
444 "ROLE_PERMISSION": "ns_instances:id:scale:",
447 "METHODS": ("POST",),
448 "ROLE_PERMISSION": "ns_instances:id:terminate:",
451 "METHODS": ("POST",),
452 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
455 "METHODS": ("POST",),
456 "ROLE_PERMISSION": "ns_instances:id:migrate:",
459 "METHODS": ("POST",),
460 "ROLE_PERMISSION": "ns_instances:id:action:",
463 "METHODS": ("POST",),
464 "ROLE_PERMISSION": "ns_instances:id:update:",
467 "METHODS": ("POST",),
468 "ROLE_PERMISSION": "ns_instances:id:verticalscale:",
474 "ROLE_PERMISSION": "ns_instances:opps:",
477 "ROLE_PERMISSION": "ns_instances:opps:id:",
479 "METHODS": ("POST",),
480 "ROLE_PERMISSION": "ns_instances:opps:cancel:",
486 "ROLE_PERMISSION": "vnf_instances:",
487 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
491 "ROLE_PERMISSION": "vnf_instances:",
492 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
495 "METHODS": ("GET", "POST"),
496 "ROLE_PERMISSION": "ns_subscriptions:",
498 "METHODS": ("GET", "DELETE"),
499 "ROLE_PERMISSION": "ns_subscriptions:id:",
507 "METHODS": ("GET", "POST"),
508 "ROLE_PERMISSION": "vnflcm_instances:",
510 "METHODS": ("GET", "DELETE"),
511 "ROLE_PERMISSION": "vnflcm_instances:id:",
513 "METHODS": ("POST",),
514 "ROLE_PERMISSION": "vnflcm_instances:id:scale:",
517 "METHODS": ("POST",),
518 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:",
521 "METHODS": ("POST",),
522 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:",
528 "ROLE_PERMISSION": "vnf_instances:opps:",
531 "ROLE_PERMISSION": "vnf_instances:opps:id:",
535 "METHODS": ("GET", "POST"),
536 "ROLE_PERMISSION": "vnflcm_subscriptions:",
538 "METHODS": ("GET", "DELETE"),
539 "ROLE_PERMISSION": "vnflcm_subscriptions:id:",
546 "netslice_templates_content": {
547 "METHODS": ("GET", "POST"),
548 "ROLE_PERMISSION": "slice_templates:",
550 "METHODS": ("GET", "PUT", "DELETE"),
551 "ROLE_PERMISSION": "slice_templates:id:",
554 "netslice_templates": {
555 "METHODS": ("GET", "POST"),
556 "ROLE_PERMISSION": "slice_templates:",
558 "METHODS": ("GET", "DELETE"),
560 "ROLE_PERMISSION": "slice_templates:id:",
562 "METHODS": ("GET", "PUT"),
563 "ROLE_PERMISSION": "slice_templates:id:content:",
566 "METHODS": ("GET",), # descriptor inside package
567 "ROLE_PERMISSION": "slice_templates:id:content:",
571 "ROLE_PERMISSION": "slice_templates:id:content:",
577 "TODO": ("GET", "POST"),
578 "<ID>": {"TODO": ("GET", "DELETE")},
584 "netslice_instances_content": {
585 "METHODS": ("GET", "POST"),
586 "ROLE_PERMISSION": "slice_instances:",
588 "METHODS": ("GET", "DELETE"),
589 "ROLE_PERMISSION": "slice_instances:id:",
592 "netslice_instances": {
593 "METHODS": ("GET", "POST"),
594 "ROLE_PERMISSION": "slice_instances:",
596 "METHODS": ("GET", "DELETE"),
597 "ROLE_PERMISSION": "slice_instances:id:",
599 "METHODS": ("POST",),
600 "ROLE_PERMISSION": "slice_instances:id:terminate:",
603 "METHODS": ("POST",),
604 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
607 "METHODS": ("POST",),
608 "ROLE_PERMISSION": "slice_instances:id:action:",
614 "ROLE_PERMISSION": "slice_instances:opps:",
617 "ROLE_PERMISSION": "slice_instances:opps:id:",
629 "ROLE_PERMISSION": "reports:id:",
639 "METHODS": ("GET", "PATCH"),
640 "ROLE_PERMISSION": "alarms:",
642 "METHODS": ("GET", "PATCH"),
643 "ROLE_PERMISSION": "alarms:id:",
651 class NbiException(Exception):
652 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
653 Exception.__init
__(self
, message
)
654 self
.http_code
= http_code
657 class Server(object):
659 # to decode bytes to str
660 reader
= getreader("utf-8")
664 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
665 self
.engine
= Engine(self
.authenticator
)
667 def _format_in(self
, kwargs
):
668 error_text
= "" # error_text must be initialized outside try
671 if cherrypy
.request
.body
.length
:
672 error_text
= "Invalid input format "
674 if "Content-Type" in cherrypy
.request
.headers
:
675 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
676 error_text
= "Invalid json format "
677 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
678 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
679 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
680 error_text
= "Invalid yaml format "
681 indata
= yaml
.safe_load(cherrypy
.request
.body
)
682 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
684 "application/binary" in cherrypy
.request
.headers
["Content-Type"]
685 or "application/gzip"
686 in cherrypy
.request
.headers
["Content-Type"]
687 or "application/zip" in cherrypy
.request
.headers
["Content-Type"]
688 or "text/plain" in cherrypy
.request
.headers
["Content-Type"]
690 indata
= cherrypy
.request
.body
# .read()
692 "multipart/form-data"
693 in cherrypy
.request
.headers
["Content-Type"]
695 if "descriptor_file" in kwargs
:
696 filecontent
= kwargs
.pop("descriptor_file")
697 if not filecontent
.file:
699 "empty file or content", HTTPStatus
.BAD_REQUEST
701 indata
= filecontent
.file # .read()
702 if filecontent
.content_type
.value
:
703 cherrypy
.request
.headers
[
705 ] = filecontent
.content_type
.value
707 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
708 # "Only 'Content-Type' of type 'application/json' or
709 # 'application/yaml' for input format are available")
710 error_text
= "Invalid yaml format "
711 indata
= yaml
.safe_load(cherrypy
.request
.body
)
712 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
714 error_text
= "Invalid yaml format "
715 indata
= yaml
.safe_load(cherrypy
.request
.body
)
716 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
721 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
724 for k
, v
in kwargs
.items():
725 if isinstance(v
, str):
730 kwargs
[k
] = yaml
.safe_load(v
)
736 or k
.endswith(".gte")
737 or k
.endswith(".lte")
746 elif v
.find(",") > 0:
747 kwargs
[k
] = v
.split(",")
748 elif isinstance(v
, (list, tuple)):
749 for index
in range(0, len(v
)):
754 v
[index
] = yaml
.safe_load(v
[index
])
759 except (ValueError, yaml
.YAMLError
) as exc
:
760 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
761 except KeyError as exc
:
763 "Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
765 except Exception as exc
:
766 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
769 def _format_out(data
, token_info
=None, _format
=None):
771 return string of dictionary data according to requested json, yaml, xml. By default json
772 :param data: response to be sent. Can be a dict, text or file
773 :param token_info: Contains among other username and project
774 :param _format: The format to be set as Content-Type if data is a file
777 accept
= cherrypy
.request
.headers
.get("Accept")
779 if accept
and "text/html" in accept
:
781 data
, cherrypy
.request
, cherrypy
.response
, token_info
783 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
785 elif hasattr(data
, "read"): # file object
787 cherrypy
.response
.headers
["Content-Type"] = _format
788 elif "b" in data
.mode
: # binariy asssumig zip
789 cherrypy
.response
.headers
["Content-Type"] = "application/zip"
791 cherrypy
.response
.headers
["Content-Type"] = "text/plain"
792 # TODO check that cherrypy close file. If not implement pending things to close per thread next
795 if "text/html" in accept
:
797 data
, cherrypy
.request
, cherrypy
.response
, token_info
799 elif "application/yaml" in accept
or "*/*" in accept
:
801 elif "application/json" in accept
or (
802 cherrypy
.response
.status
and cherrypy
.response
.status
>= 300
804 cherrypy
.response
.headers
[
806 ] = "application/json; charset=utf-8"
807 a
= json
.dumps(data
, indent
=4) + "\n"
808 return a
.encode("utf8")
809 cherrypy
.response
.headers
["Content-Type"] = "application/yaml"
810 return yaml
.safe_dump(
814 default_flow_style
=False,
818 ) # , canonical=True, default_style='"'
821 def index(self
, *args
, **kwargs
):
824 if cherrypy
.request
.method
== "GET":
825 token_info
= self
.authenticator
.authorize()
826 outdata
= token_info
# Home page
828 raise cherrypy
.HTTPError(
829 HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
830 "Method {} not allowed for tokens".format(cherrypy
.request
.method
),
833 return self
._format
_out
(outdata
, token_info
)
835 except (EngineException
, AuthException
) as e
:
836 # cherrypy.log("index Exception {}".format(e))
837 cherrypy
.response
.status
= e
.http_code
.value
838 return self
._format
_out
("Welcome to OSM!", token_info
)
841 def version(self
, *args
, **kwargs
):
842 # TODO consider to remove and provide version using the static version file
844 if cherrypy
.request
.method
!= "GET":
846 "Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
850 "Invalid URL or query string for version",
851 HTTPStatus
.METHOD_NOT_ALLOWED
,
853 # TODO include version of other modules, pick up from some kafka admin message
854 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
855 return self
._format
_out
(osm_nbi_version
)
856 except NbiException
as e
:
857 cherrypy
.response
.status
= e
.http_code
.value
859 "code": e
.http_code
.name
,
860 "status": e
.http_code
.value
,
863 return self
._format
_out
(problem_details
, None)
868 "user_domain_name": cherrypy
.tree
.apps
["/osm"]
869 .config
["authentication"]
870 .get("user_domain_name"),
871 "project_domain_name": cherrypy
.tree
.apps
["/osm"]
872 .config
["authentication"]
873 .get("project_domain_name"),
875 return self
._format
_out
(domains
)
876 except NbiException
as e
:
877 cherrypy
.response
.status
= e
.http_code
.value
879 "code": e
.http_code
.name
,
880 "status": e
.http_code
.value
,
883 return self
._format
_out
(problem_details
, None)
886 def _format_login(token_info
):
888 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
890 :param token_info: Dictionary with token content
893 cherrypy
.request
.login
= token_info
.get("username", "-")
894 if token_info
.get("project_name"):
895 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
896 if token_info
.get("id"):
897 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
899 # NS Fault Management
911 if topic
== "alarms":
913 method
= cherrypy
.request
.method
914 role_permission
= self
._check
_valid
_url
_method
(
915 method
, "nsfm", version
, topic
, None, None, *args
917 query_string_operations
= self
._extract
_query
_string
_operations
(
921 self
.authenticator
.authorize(
922 role_permission
, query_string_operations
, None
925 # to handle get request
926 if cherrypy
.request
.method
== "GET":
927 # if request is on basis of uuid
928 if uuid
and uuid
!= "None":
930 alarm
= self
.engine
.db
.get_one("alarms", {"uuid": uuid
})
931 alarm_action
= self
.engine
.db
.get_one(
932 "alarms_action", {"uuid": uuid
}
934 alarm
.update(alarm_action
)
935 vnf
= self
.engine
.db
.get_one(
936 "vnfrs", {"nsr-id-ref": alarm
["tags"]["ns_id"]}
938 alarm
["vnf-id"] = vnf
["_id"]
939 return self
._format
_out
(str(alarm
))
941 return self
._format
_out
("Please provide valid alarm uuid")
942 elif ns_id
and ns_id
!= "None":
943 # if request is on basis of ns_id
945 alarms
= self
.engine
.db
.get_list(
946 "alarms", {"tags.ns_id": ns_id
}
949 alarm_action
= self
.engine
.db
.get_one(
950 "alarms_action", {"uuid": alarm
["uuid"]}
952 alarm
.update(alarm_action
)
953 return self
._format
_out
(str(alarms
))
955 return self
._format
_out
("Please provide valid ns id")
957 # to return only alarm which are related to given project
958 project
= self
.engine
.db
.get_one(
959 "projects", {"name": project_name
}
961 project_id
= project
.get("_id")
962 ns_list
= self
.engine
.db
.get_list(
963 "nsrs", {"_admin.projects_read": project_id
}
967 ns_ids
.append(ns
.get("_id"))
968 alarms
= self
.engine
.db
.get_list("alarms")
972 if alarm
["tags"]["ns_id"] in ns_ids
974 for alrm
in alarm_list
:
975 action
= self
.engine
.db
.get_one(
976 "alarms_action", {"uuid": alrm
.get("uuid")}
979 return self
._format
_out
(str(alarm_list
))
980 # to handle patch request for alarm update
981 elif cherrypy
.request
.method
== "PATCH":
982 data
= yaml
.safe_load(cherrypy
.request
.body
)
984 # check if uuid is valid
985 self
.engine
.db
.get_one("alarms", {"uuid": data
.get("uuid")})
987 return self
._format
_out
("Please provide valid alarm uuid.")
988 if data
.get("is_enable") is not None:
989 if data
.get("is_enable"):
992 alarm_status
= "disabled"
993 self
.engine
.db
.set_one(
995 {"uuid": data
.get("uuid")},
996 {"alarm_status": alarm_status
},
999 self
.engine
.db
.set_one(
1001 {"uuid": data
.get("uuid")},
1002 {"threshold": data
.get("threshold")},
1004 return self
._format
_out
("Alarm updated")
1005 except Exception as e
:
1019 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1020 http_code_name
= e
.http_code
.name
1021 cherrypy
.log("Exception {}".format(e
))
1024 cherrypy
.response
.status
1025 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1026 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1027 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1029 "code": http_code_name
,
1030 "status": http_code_value
,
1033 return self
._format
_out
(problem_details
)
1036 def token(self
, method
, token_id
=None, kwargs
=None):
1038 # self.engine.load_dbase(cherrypy.request.app.config)
1039 indata
= self
._format
_in
(kwargs
)
1040 if not isinstance(indata
, dict):
1042 "Expected application/yaml or application/json Content-Type",
1043 HTTPStatus
.BAD_REQUEST
,
1047 token_info
= self
.authenticator
.authorize()
1049 self
._format
_login
(token_info
)
1051 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
1053 outdata
= self
.authenticator
.get_token_list(token_info
)
1054 elif method
== "POST":
1056 token_info
= self
.authenticator
.authorize()
1060 indata
.update(kwargs
)
1061 # This is needed to log the user when authentication fails
1062 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
1063 outdata
= token_info
= self
.authenticator
.new_token(
1064 token_info
, indata
, cherrypy
.request
.remote
1066 cherrypy
.session
["Authorization"] = outdata
["_id"] # pylint: disable=E1101
1067 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
1069 self
._format
_login
(token_info
)
1070 # password expiry check
1071 if self
.authenticator
.check_password_expiry(outdata
):
1073 "id": outdata
["id"],
1074 "message": "change_password",
1075 "user_id": outdata
["user_id"],
1077 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1078 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1082 "name": "User Login",
1083 "sourceUserName": token_info
.get("username"),
1084 "message": "User Logged In, Project={} Outcome=Success".format(
1085 token_info
.get("project_name")
1089 cherrypy
.log("{}".format(cef_logger
))
1090 elif method
== "DELETE":
1091 if not token_id
and "id" in kwargs
:
1092 token_id
= kwargs
["id"]
1094 token_info
= self
.authenticator
.authorize()
1096 self
._format
_login
(token_info
)
1097 token_id
= token_info
["_id"]
1098 if current_backend
!= "keystone":
1099 token_details
= self
.engine
.db
.get_one("tokens", {"_id": token_id
})
1100 current_user
= token_details
.get("username")
1101 current_project
= token_details
.get("project_name")
1103 current_user
= "keystone backend"
1104 current_project
= "keystone backend"
1105 outdata
= self
.authenticator
.del_token(token_id
)
1107 cherrypy
.session
["Authorization"] = "logout" # pylint: disable=E1101
1111 "name": "User Logout",
1112 "sourceUserName": current_user
,
1113 "message": "User Logged Out, Project={} Outcome=Success".format(
1118 cherrypy
.log("{}".format(cef_logger
))
1119 # cherrypy.response.cookie["Authorization"] = token_id
1120 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1123 "Method {} not allowed for token".format(method
),
1124 HTTPStatus
.METHOD_NOT_ALLOWED
,
1126 return self
._format
_out
(outdata
, token_info
)
1129 def test(self
, *args
, **kwargs
):
1130 if not cherrypy
.config
.get("server.enable_test") or (
1131 isinstance(cherrypy
.config
["server.enable_test"], str)
1132 and cherrypy
.config
["server.enable_test"].lower() == "false"
1134 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
1135 return "test URL is disabled"
1137 if args
and args
[0] == "help":
1139 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1140 "sleep/<time>\nmessage/topic\n</pre></html>"
1143 elif args
and args
[0] == "init":
1145 # self.engine.load_dbase(cherrypy.request.app.config)
1146 pid
= self
.authenticator
.create_admin_project()
1147 self
.authenticator
.create_admin_user(pid
)
1148 return "Done. User 'admin', password 'admin' created"
1150 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1151 return self
._format
_out
("Database already initialized")
1152 elif args
and args
[0] == "file":
1153 return cherrypy
.lib
.static
.serve_file(
1154 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1],
1158 elif args
and args
[0] == "file2":
1160 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1]
1162 f
= open(f_path
, "r")
1163 cherrypy
.response
.headers
["Content-type"] = "text/plain"
1166 elif len(args
) == 2 and args
[0] == "db-clear":
1167 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
1168 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
1169 elif len(args
) and args
[0] == "fs-clear":
1171 folders
= (args
[1],)
1173 folders
= self
.engine
.fs
.dir_ls(".")
1174 for folder
in folders
:
1175 self
.engine
.fs
.file_delete(folder
)
1176 return ",".join(folders
) + " folders deleted\n"
1177 elif args
and args
[0] == "login":
1178 if not cherrypy
.request
.headers
.get("Authorization"):
1179 cherrypy
.response
.headers
[
1181 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
1182 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1183 elif args
and args
[0] == "login2":
1184 if not cherrypy
.request
.headers
.get("Authorization"):
1185 cherrypy
.response
.headers
[
1187 ] = 'Bearer realm="Access to OSM site"'
1188 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1189 elif args
and args
[0] == "sleep":
1192 sleep_time
= int(args
[1])
1194 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1195 return self
._format
_out
("Database already initialized")
1196 thread_info
= cherrypy
.thread_data
1198 time
.sleep(sleep_time
)
1200 elif len(args
) >= 2 and args
[0] == "message":
1201 main_topic
= args
[1]
1202 return_text
= "<html><pre>{} ->\n".format(main_topic
)
1204 if cherrypy
.request
.method
== "POST":
1205 to_send
= yaml
.safe_load(cherrypy
.request
.body
)
1206 for k
, v
in to_send
.items():
1207 self
.engine
.msg
.write(main_topic
, k
, v
)
1208 return_text
+= " {}: {}\n".format(k
, v
)
1209 elif cherrypy
.request
.method
== "GET":
1210 for k
, v
in kwargs
.items():
1211 v_dict
= yaml
.safe_load(v
)
1212 self
.engine
.msg
.write(main_topic
, k
, v_dict
)
1213 return_text
+= " {}: {}\n".format(k
, v_dict
)
1214 except Exception as e
:
1215 return_text
+= "Error: " + str(e
)
1216 return_text
+= "</pre></html>\n"
1220 "<html><pre>\nheaders:\n args: {}\n".format(args
)
1221 + " kwargs: {}\n".format(kwargs
)
1222 + " headers: {}\n".format(cherrypy
.request
.headers
)
1223 + " path_info: {}\n".format(cherrypy
.request
.path_info
)
1224 + " query_string: {}\n".format(cherrypy
.request
.query_string
)
1225 + " session: {}\n".format(cherrypy
.session
) # pylint: disable=E1101
1226 + " cookie: {}\n".format(cherrypy
.request
.cookie
)
1227 + " method: {}\n".format(cherrypy
.request
.method
)
1228 + " session: {}\n".format(
1229 cherrypy
.session
.get("fieldname") # pylint: disable=E1101
1233 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
1234 if cherrypy
.request
.body
.length
:
1235 return_text
+= " content: {}\n".format(
1237 cherrypy
.request
.body
.read(
1238 int(cherrypy
.request
.headers
.get("Content-Length", 0))
1243 return_text
+= "thread: {}\n".format(thread_info
)
1244 return_text
+= "</pre></html>"
1248 def _check_valid_url_method(method
, *args
):
1251 "URL must contain at least 'main_topic/version/topic'",
1252 HTTPStatus
.METHOD_NOT_ALLOWED
,
1255 reference
= valid_url_methods
1259 if not isinstance(reference
, dict):
1261 "URL contains unexpected extra items '{}'".format(arg
),
1262 HTTPStatus
.METHOD_NOT_ALLOWED
,
1265 if arg
in reference
:
1266 reference
= reference
[arg
]
1267 elif "<ID>" in reference
:
1268 reference
= reference
["<ID>"]
1269 elif "*" in reference
:
1270 # if there is content
1272 reference
= reference
["*"]
1276 "Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
1278 if "TODO" in reference
and method
in reference
["TODO"]:
1280 "Method {} not supported yet for this URL".format(method
),
1281 HTTPStatus
.NOT_IMPLEMENTED
,
1283 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
1285 "Method {} not supported for this URL".format(method
),
1286 HTTPStatus
.METHOD_NOT_ALLOWED
,
1288 return reference
["ROLE_PERMISSION"] + method
.lower()
1291 def _set_location_header(main_topic
, version
, topic
, id):
1293 Insert response header Location with the URL of created item base on URL params
1300 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1301 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(
1302 main_topic
, version
, topic
, id
1307 def _extract_query_string_operations(kwargs
, method
):
1313 query_string_operations
= []
1315 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1316 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
1317 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
1318 return query_string_operations
1321 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
1323 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1324 Check that users has rights to use them and returs the admin_query
1325 :param token_info: token_info rights obtained by token
1326 :param kwargs: query string input.
1327 :param method: http method: GET, POSST, PUT, ...
1329 :return: admin_query dictionary with keys:
1330 public: True, False or None
1331 force: True or False
1332 project_id: tuple with projects used for accessing an element
1333 set_project: tuple with projects that a created element will belong to
1334 method: show, list, delete, write
1338 "project_id": (token_info
["project_id"],),
1339 "username": token_info
["username"],
1340 "admin": token_info
["admin"],
1342 "allow_show_user_project_role": token_info
["allow_show_user_project_role"],
1346 if "FORCE" in kwargs
:
1348 kwargs
["FORCE"].lower() != "false"
1349 ): # if None or True set force to True
1350 admin_query
["force"] = True
1353 if "PUBLIC" in kwargs
:
1355 kwargs
["PUBLIC"].lower() != "false"
1356 ): # if None or True set public to True
1357 admin_query
["public"] = True
1359 admin_query
["public"] = False
1360 del kwargs
["PUBLIC"]
1362 if "ADMIN" in kwargs
:
1363 behave_as
= kwargs
.pop("ADMIN")
1364 if behave_as
.lower() != "false":
1365 if not token_info
["admin"]:
1367 "Only admin projects can use 'ADMIN' query string",
1368 HTTPStatus
.UNAUTHORIZED
,
1371 not behave_as
or behave_as
.lower() == "true"
1372 ): # convert True, None to empty list
1373 admin_query
["project_id"] = ()
1374 elif isinstance(behave_as
, (list, tuple)):
1375 admin_query
["project_id"] = behave_as
1376 else: # isinstance(behave_as, str)
1377 admin_query
["project_id"] = (behave_as
,)
1378 if "SET_PROJECT" in kwargs
:
1379 set_project
= kwargs
.pop("SET_PROJECT")
1381 admin_query
["set_project"] = list(admin_query
["project_id"])
1383 if isinstance(set_project
, str):
1384 set_project
= (set_project
,)
1385 if admin_query
["project_id"]:
1386 for p
in set_project
:
1387 if p
not in admin_query
["project_id"]:
1389 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1390 "'ADMIN='{p}'".format(p
=p
),
1391 HTTPStatus
.UNAUTHORIZED
,
1393 admin_query
["set_project"] = set_project
1396 # if "PROJECT_READ" in kwargs:
1397 # admin_query["project"] = kwargs.pop("project")
1398 # if admin_query["project"] == token_info["project_id"]:
1401 admin_query
["method"] = "show"
1403 admin_query
["method"] = "list"
1404 elif method
== "DELETE":
1405 admin_query
["method"] = "delete"
1407 admin_query
["method"] = "write"
1427 engine_session
= None
1432 "DELETE": "Deleting",
1434 "PATCH": "Updating",
1437 if not main_topic
or not version
or not topic
:
1439 "URL must contain at least 'main_topic/version/topic'",
1440 HTTPStatus
.METHOD_NOT_ALLOWED
,
1442 if main_topic
not in (
1454 "URL main_topic '{}' not supported".format(main_topic
),
1455 HTTPStatus
.METHOD_NOT_ALLOWED
,
1459 "URL version '{}' not supported".format(version
),
1460 HTTPStatus
.METHOD_NOT_ALLOWED
,
1467 and "METHOD" in kwargs
1468 and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1470 method
= kwargs
.pop("METHOD")
1472 method
= cherrypy
.request
.method
1474 role_permission
= self
._check
_valid
_url
_method
(
1475 method
, main_topic
, version
, topic
, _id
, item
, *args
1477 query_string_operations
= self
._extract
_query
_string
_operations
(
1480 if main_topic
== "admin" and topic
== "tokens":
1481 return self
.token(method
, _id
, kwargs
)
1482 token_info
= self
.authenticator
.authorize(
1483 role_permission
, query_string_operations
, _id
1485 if main_topic
== "admin" and topic
== "domains":
1486 return self
.domain()
1487 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
1488 indata
= self
._format
_in
(kwargs
)
1489 engine_topic
= topic
1491 if item
and topic
!= "pm_jobs":
1494 if main_topic
== "nsd":
1495 engine_topic
= "nsds"
1496 elif main_topic
== "vnfpkgm":
1497 engine_topic
= "vnfds"
1498 if topic
== "vnfpkg_op_occs":
1499 engine_topic
= "vnfpkgops"
1500 if topic
== "vnf_packages" and item
== "action":
1501 engine_topic
= "vnfpkgops"
1502 elif main_topic
== "nslcm":
1503 engine_topic
= "nsrs"
1504 if topic
== "ns_lcm_op_occs":
1505 engine_topic
= "nslcmops"
1506 if topic
== "vnfrs" or topic
== "vnf_instances":
1507 engine_topic
= "vnfrs"
1508 elif main_topic
== "vnflcm":
1509 if topic
== "vnf_lcm_op_occs":
1510 engine_topic
= "vnflcmops"
1511 elif main_topic
== "nst":
1512 engine_topic
= "nsts"
1513 elif main_topic
== "nsilcm":
1514 engine_topic
= "nsis"
1515 if topic
== "nsi_lcm_op_occs":
1516 engine_topic
= "nsilcmops"
1517 elif main_topic
== "pdu":
1518 engine_topic
= "pdus"
1520 engine_topic
== "vims"
1521 ): # TODO this is for backward compatibility, it will be removed in the future
1522 engine_topic
= "vim_accounts"
1524 if topic
== "subscriptions":
1525 engine_topic
= main_topic
+ "_" + topic
1537 if item
in ("vnfd", "nsd", "nst"):
1538 path
= "$DESCRIPTOR"
1541 elif item
== "artifacts":
1545 file, _format
= self
.engine
.get_file(
1550 cherrypy
.request
.headers
.get("Accept"),
1554 outdata
= self
.engine
.get_item_list(
1555 engine_session
, engine_topic
, kwargs
, api_req
=True
1558 if item
== "reports":
1559 # TODO check that project_id (_id in this context) has permissions
1562 if "vcaStatusRefresh" in kwargs
:
1563 filter_q
= {"vcaStatusRefresh": kwargs
["vcaStatusRefresh"]}
1564 outdata
= self
.engine
.get_item(
1565 engine_session
, engine_topic
, _id
, filter_q
, True
1568 elif method
== "POST":
1569 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1571 "ns_descriptors_content",
1572 "vnf_packages_content",
1573 "netslice_templates_content",
1575 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1577 _id
, _
= self
.engine
.new_item(
1583 cherrypy
.request
.headers
,
1585 completed
= self
.engine
.upload_content(
1591 cherrypy
.request
.headers
,
1594 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1596 cherrypy
.response
.headers
["Transaction-Id"] = _id
1597 outdata
= {"id": _id
}
1598 elif topic
== "ns_instances_content":
1600 _id
, _
= self
.engine
.new_item(
1601 rollback
, engine_session
, engine_topic
, indata
, kwargs
1604 indata
["lcmOperationType"] = "instantiate"
1605 indata
["nsInstanceId"] = _id
1606 nslcmop_id
, _
= self
.engine
.new_item(
1607 rollback
, engine_session
, "nslcmops", indata
, None
1609 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1610 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1611 elif topic
== "ns_instances" and item
:
1612 indata
["lcmOperationType"] = item
1613 indata
["nsInstanceId"] = _id
1614 _id
, _
= self
.engine
.new_item(
1615 rollback
, engine_session
, "nslcmops", indata
, kwargs
1617 self
._set
_location
_header
(
1618 main_topic
, version
, "ns_lcm_op_occs", _id
1620 outdata
= {"id": _id
}
1621 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1622 elif topic
== "netslice_instances_content":
1623 # creates NetSlice_Instance_record (NSIR)
1624 _id
, _
= self
.engine
.new_item(
1625 rollback
, engine_session
, engine_topic
, indata
, kwargs
1627 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1628 indata
["lcmOperationType"] = "instantiate"
1629 indata
["netsliceInstanceId"] = _id
1630 nsilcmop_id
, _
= self
.engine
.new_item(
1631 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1633 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1634 elif topic
== "netslice_instances" and item
:
1635 indata
["lcmOperationType"] = item
1636 indata
["netsliceInstanceId"] = _id
1637 _id
, _
= self
.engine
.new_item(
1638 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1640 self
._set
_location
_header
(
1641 main_topic
, version
, "nsi_lcm_op_occs", _id
1643 outdata
= {"id": _id
}
1644 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1645 elif topic
== "vnf_packages" and item
== "action":
1646 indata
["lcmOperationType"] = item
1647 indata
["vnfPkgId"] = _id
1648 _id
, _
= self
.engine
.new_item(
1649 rollback
, engine_session
, "vnfpkgops", indata
, kwargs
1651 self
._set
_location
_header
(
1652 main_topic
, version
, "vnfpkg_op_occs", _id
1654 outdata
= {"id": _id
}
1655 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1656 elif topic
== "subscriptions":
1657 _id
, _
= self
.engine
.new_item(
1658 rollback
, engine_session
, engine_topic
, indata
, kwargs
1660 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1662 link
["self"] = cherrypy
.response
.headers
["Location"]
1665 "filter": indata
["filter"],
1666 "callbackUri": indata
["CallbackUri"],
1669 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1670 elif topic
== "vnf_instances" and item
:
1671 indata
["lcmOperationType"] = item
1672 indata
["vnfInstanceId"] = _id
1673 _id
, _
= self
.engine
.new_item(
1674 rollback
, engine_session
, "vnflcmops", indata
, kwargs
1676 self
._set
_location
_header
(
1677 main_topic
, version
, "vnf_lcm_op_occs", _id
1679 outdata
= {"id": _id
}
1680 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1681 elif topic
== "ns_lcm_op_occs" and item
== "cancel":
1682 indata
["nsLcmOpOccId"] = _id
1683 self
.engine
.cancel_item(
1684 rollback
, engine_session
, "nslcmops", indata
, None
1686 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1687 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1689 _id
, op_id
= self
.engine
.new_item(
1695 cherrypy
.request
.headers
,
1697 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1698 outdata
= {"id": _id
}
1700 outdata
["op_id"] = op_id
1701 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1702 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1704 elif method
== "DELETE":
1706 outdata
= self
.engine
.del_item_list(
1707 engine_session
, engine_topic
, kwargs
1709 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1710 else: # len(args) > 1
1711 # for NS NSI generate an operation
1713 if topic
== "ns_instances_content" and not engine_session
["force"]:
1715 "lcmOperationType": "terminate",
1716 "nsInstanceId": _id
,
1719 op_id
, _
= self
.engine
.new_item(
1720 rollback
, engine_session
, "nslcmops", nslcmop_desc
, kwargs
1723 outdata
= {"_id": op_id
}
1725 topic
== "netslice_instances_content"
1726 and not engine_session
["force"]
1729 "lcmOperationType": "terminate",
1730 "netsliceInstanceId": _id
,
1733 op_id
, _
= self
.engine
.new_item(
1734 rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None
1737 outdata
= {"_id": op_id
}
1738 # if there is not any deletion in process, delete
1740 op_id
= self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1742 outdata
= {"op_id": op_id
}
1743 cherrypy
.response
.status
= (
1744 HTTPStatus
.ACCEPTED
.value
1746 else HTTPStatus
.NO_CONTENT
.value
1749 elif method
in ("PUT", "PATCH"):
1751 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1753 "Nothing to update. Provide payload and/or query string",
1754 HTTPStatus
.BAD_REQUEST
,
1757 item
in ("nsd_content", "package_content", "nst_content")
1760 completed
= self
.engine
.upload_content(
1766 cherrypy
.request
.headers
,
1769 cherrypy
.response
.headers
["Transaction-Id"] = id
1771 op_id
= self
.engine
.edit_item(
1772 engine_session
, engine_topic
, _id
, indata
, kwargs
1776 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1777 outdata
= {"op_id": op_id
}
1779 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1783 "Method {} not allowed".format(method
),
1784 HTTPStatus
.METHOD_NOT_ALLOWED
,
1787 # if Role information changes, it is needed to reload the information of roles
1788 if topic
== "roles" and method
!= "GET":
1789 self
.authenticator
.load_operation_to_allowed_roles()
1793 and method
== "DELETE"
1794 or topic
in ["users", "roles"]
1795 and method
in ["PUT", "PATCH", "DELETE"]
1797 self
.authenticator
.remove_token_from_cache()
1799 if item
is not None:
1803 "name": "User Operation",
1804 "sourceUserName": token_info
.get("username"),
1805 "message": "Performing {} operation on {} {}, Project={} Outcome=Success".format(
1809 token_info
.get("project_name"),
1813 cherrypy
.log("{}".format(cef_logger
))
1818 "name": "User Operation",
1819 "sourceUserName": token_info
.get("username"),
1820 "message": "{} {} {}, Project={} Outcome=Success".format(
1821 log_mapping
[method
],
1824 token_info
.get("project_name"),
1828 cherrypy
.log("{}".format(cef_logger
))
1829 return self
._format
_out
(outdata
, token_info
, _format
)
1830 except Exception as e
:
1844 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1845 http_code_name
= e
.http_code
.name
1846 cherrypy
.log("Exception {}".format(e
))
1849 cherrypy
.response
.status
1850 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1851 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1852 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1853 if hasattr(outdata
, "close"): # is an open file
1857 for rollback_item
in rollback
:
1859 if rollback_item
.get("operation") == "set":
1860 self
.engine
.db
.set_one(
1861 rollback_item
["topic"],
1862 {"_id": rollback_item
["_id"]},
1863 rollback_item
["content"],
1864 fail_on_empty
=False,
1866 elif rollback_item
.get("operation") == "del_list":
1867 self
.engine
.db
.del_list(
1868 rollback_item
["topic"],
1869 rollback_item
["filter"],
1872 self
.engine
.db
.del_one(
1873 rollback_item
["topic"],
1874 {"_id": rollback_item
["_id"]},
1875 fail_on_empty
=False,
1877 except Exception as e2
:
1878 rollback_error_text
= "Rollback Exception {}: {}".format(
1881 cherrypy
.log(rollback_error_text
)
1882 error_text
+= ". " + rollback_error_text
1883 # if isinstance(e, MsgException):
1884 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1885 # engine_topic[:-1], method, error_text)
1887 "code": http_code_name
,
1888 "status": http_code_value
,
1889 "detail": error_text
,
1891 if item
is not None and token_info
is not None:
1895 "name": "User Operation",
1896 "sourceUserName": token_info
.get("username", None),
1897 "message": "Performing {} operation on {} {}, Project={} Outcome=Failure".format(
1901 token_info
.get("project_name", None),
1906 cherrypy
.log("{}".format(cef_logger
))
1907 elif token_info
is not None:
1911 "name": "User Operation",
1912 "sourceUserName": token_info
.get("username", None),
1913 "message": "{} {} {}, Project={} Outcome=Failure".format(
1917 token_info
.get("project_name", None),
1922 cherrypy
.log("{}".format(cef_logger
))
1923 return self
._format
_out
(problem_details
, token_info
)
1924 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1927 self
._format
_login
(token_info
)
1928 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1929 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1930 if outdata
.get(logging_id
):
1931 cherrypy
.request
.login
+= ";{}={}".format(
1932 logging_id
, outdata
[logging_id
][:36]
1936 def _start_service():
1938 Callback function called when cherrypy.engine starts
1939 Override configuration with env variables
1940 Set database, storage, message configuration
1941 Init database with admin/admin user password
1944 global subscription_thread
1946 global current_backend
1947 cherrypy
.log
.error("Starting osm_nbi")
1948 # update general cherrypy configuration
1951 engine_config
= cherrypy
.tree
.apps
["/osm"].config
1952 for k
, v
in environ
.items():
1953 if k
== "OSMNBI_USER_MANAGEMENT":
1954 feature_state
= eval(v
.title())
1955 engine_config
["authentication"]["user_management"] = feature_state
1956 if not k
.startswith("OSMNBI_"):
1958 k1
, _
, k2
= k
[7:].lower().partition("_")
1962 # update static configuration
1963 if k
== "OSMNBI_STATIC_DIR":
1964 engine_config
["/static"]["tools.staticdir.dir"] = v
1965 engine_config
["/static"]["tools.staticdir.on"] = True
1966 elif k
== "OSMNBI_SOCKET_PORT" or k
== "OSMNBI_SERVER_PORT":
1967 update_dict
["server.socket_port"] = int(v
)
1968 elif k
== "OSMNBI_SOCKET_HOST" or k
== "OSMNBI_SERVER_HOST":
1969 update_dict
["server.socket_host"] = v
1970 elif k1
in ("server", "test", "auth", "log"):
1971 update_dict
[k1
+ "." + k2
] = v
1972 elif k1
in ("message", "database", "storage", "authentication"):
1973 # k2 = k2.replace('_', '.')
1974 if k2
in ("port", "db_port"):
1975 engine_config
[k1
][k2
] = int(v
)
1977 engine_config
[k1
][k2
] = v
1979 except ValueError as e
:
1980 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1981 except Exception as e
:
1983 "WARNING: skipping environ '{}' on exception '{}'".format(k
, e
)
1987 cherrypy
.config
.update(update_dict
)
1988 engine_config
["global"].update(update_dict
)
1991 log_format_simple
= (
1992 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1994 log_formatter_simple
= logging
.Formatter(
1995 log_format_simple
, datefmt
="%Y-%m-%dT%H:%M:%S"
1997 logger_server
= logging
.getLogger("cherrypy.error")
1998 logger_access
= logging
.getLogger("cherrypy.access")
1999 logger_cherry
= logging
.getLogger("cherrypy")
2000 logger_nbi
= logging
.getLogger("nbi")
2002 if "log.file" in engine_config
["global"]:
2003 file_handler
= logging
.handlers
.RotatingFileHandler(
2004 engine_config
["global"]["log.file"], maxBytes
=100e6
, backupCount
=9, delay
=0
2006 file_handler
.setFormatter(log_formatter_simple
)
2007 logger_cherry
.addHandler(file_handler
)
2008 logger_nbi
.addHandler(file_handler
)
2009 # log always to standard output
2010 for format_
, logger
in {
2011 "nbi.server %(filename)s:%(lineno)s": logger_server
,
2012 "nbi.access %(filename)s:%(lineno)s": logger_access
,
2013 "%(name)s %(filename)s:%(lineno)s": logger_nbi
,
2015 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
2016 log_formatter_cherry
= logging
.Formatter(
2017 log_format_cherry
, datefmt
="%Y-%m-%dT%H:%M:%S"
2019 str_handler
= logging
.StreamHandler()
2020 str_handler
.setFormatter(log_formatter_cherry
)
2021 logger
.addHandler(str_handler
)
2023 if engine_config
["global"].get("log.level"):
2024 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
2025 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
2027 # logging other modules
2028 for k1
, logname
in {
2029 "message": "nbi.msg",
2030 "database": "nbi.db",
2031 "storage": "nbi.fs",
2033 engine_config
[k1
]["logger_name"] = logname
2034 logger_module
= logging
.getLogger(logname
)
2035 if "logfile" in engine_config
[k1
]:
2036 file_handler
= logging
.handlers
.RotatingFileHandler(
2037 engine_config
[k1
]["logfile"], maxBytes
=100e6
, backupCount
=9, delay
=0
2039 file_handler
.setFormatter(log_formatter_simple
)
2040 logger_module
.addHandler(file_handler
)
2041 if "loglevel" in engine_config
[k1
]:
2042 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
2043 # TODO add more entries, e.g.: storage
2044 cherrypy
.tree
.apps
["/osm"].root
.engine
.start(engine_config
)
2045 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.start(engine_config
)
2046 cherrypy
.tree
.apps
["/osm"].root
.engine
.init_db(target_version
=database_version
)
2047 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.init_db(
2048 target_version
=auth_database_version
2051 cef_logger
= cef_event_builder(engine_config
["authentication"])
2053 # start subscriptions thread:
2054 subscription_thread
= SubscriptionThread(
2055 config
=engine_config
, engine
=nbi_server
.engine
2057 subscription_thread
.start()
2058 # Do not capture except SubscriptionException
2060 backend
= engine_config
["authentication"]["backend"]
2061 current_backend
= backend
2063 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
2064 nbi_version
, nbi_version_date
, backend
2069 def _stop_service():
2071 Callback function called when cherrypy.engine stops
2072 TODO: Ending database connections.
2074 global subscription_thread
2075 if subscription_thread
:
2076 subscription_thread
.terminate()
2077 subscription_thread
= None
2078 cherrypy
.tree
.apps
["/osm"].root
.engine
.stop()
2079 cherrypy
.log
.error("Stopping osm_nbi")
2082 def nbi(config_file
):
2086 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
2087 # 'tools.sessions.on': True,
2088 # 'tools.response_headers.on': True,
2089 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
2092 # cherrypy.Server.ssl_module = 'builtin'
2093 # cherrypy.Server.ssl_certificate = "http/cert.pem"
2094 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
2095 # cherrypy.Server.thread_pool = 10
2096 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
2098 # cherrypy.config.update({'tools.auth_basic.on': True,
2099 # 'tools.auth_basic.realm': 'localhost',
2100 # 'tools.auth_basic.checkpassword': validate_password})
2101 nbi_server
= Server()
2102 cherrypy
.engine
.subscribe("start", _start_service
)
2103 cherrypy
.engine
.subscribe("stop", _stop_service
)
2104 cherrypy
.quickstart(nbi_server
, "/osm", config_file
)
2109 """Usage: {} [options]
2110 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
2111 -h|--help: shows this help
2116 # --log-socket-host HOST: send logs to this host")
2117 # --log-socket-port PORT: send logs using this port (default: 9022)")
2120 if __name__
== "__main__":
2122 # load parameters and configuration
2123 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
2124 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
2127 if o
in ("-h", "--help"):
2130 elif o
in ("-c", "--config"):
2132 # elif o == "--log-socket-port":
2133 # log_socket_port = a
2134 # elif o == "--log-socket-host":
2135 # log_socket_host = a
2136 # elif o == "--log-file":
2139 assert False, "Unhandled option"
2141 if not path
.isfile(config_file
):
2143 "configuration file '{}' that not exist".format(config_file
),
2148 for config_file
in (
2149 __file__
[: __file__
.rfind(".")] + ".cfg",
2153 if path
.isfile(config_file
):
2157 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
2162 except getopt
.GetoptError
as e
:
2163 print(str(e
), file=sys
.stderr
)