5e043718d5a28b42070391bec4610c5d548da806
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:",
482 "ROLE_PERMISSION": "vnf_instances:",
483 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
487 "ROLE_PERMISSION": "vnf_instances:",
488 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
491 "METHODS": ("GET", "POST"),
492 "ROLE_PERMISSION": "ns_subscriptions:",
494 "METHODS": ("GET", "DELETE"),
495 "ROLE_PERMISSION": "ns_subscriptions:id:",
503 "METHODS": ("GET", "POST"),
504 "ROLE_PERMISSION": "vnflcm_instances:",
506 "METHODS": ("GET", "DELETE"),
507 "ROLE_PERMISSION": "vnflcm_instances:id:",
509 "METHODS": ("POST",),
510 "ROLE_PERMISSION": "vnflcm_instances:id:scale:",
513 "METHODS": ("POST",),
514 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:",
517 "METHODS": ("POST",),
518 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:",
524 "ROLE_PERMISSION": "vnf_instances:opps:",
527 "ROLE_PERMISSION": "vnf_instances:opps:id:",
531 "METHODS": ("GET", "POST"),
532 "ROLE_PERMISSION": "vnflcm_subscriptions:",
534 "METHODS": ("GET", "DELETE"),
535 "ROLE_PERMISSION": "vnflcm_subscriptions:id:",
542 "netslice_templates_content": {
543 "METHODS": ("GET", "POST"),
544 "ROLE_PERMISSION": "slice_templates:",
546 "METHODS": ("GET", "PUT", "DELETE"),
547 "ROLE_PERMISSION": "slice_templates:id:",
550 "netslice_templates": {
551 "METHODS": ("GET", "POST"),
552 "ROLE_PERMISSION": "slice_templates:",
554 "METHODS": ("GET", "DELETE"),
556 "ROLE_PERMISSION": "slice_templates:id:",
558 "METHODS": ("GET", "PUT"),
559 "ROLE_PERMISSION": "slice_templates:id:content:",
562 "METHODS": ("GET",), # descriptor inside package
563 "ROLE_PERMISSION": "slice_templates:id:content:",
567 "ROLE_PERMISSION": "slice_templates:id:content:",
573 "TODO": ("GET", "POST"),
574 "<ID>": {"TODO": ("GET", "DELETE")},
580 "netslice_instances_content": {
581 "METHODS": ("GET", "POST"),
582 "ROLE_PERMISSION": "slice_instances:",
584 "METHODS": ("GET", "DELETE"),
585 "ROLE_PERMISSION": "slice_instances:id:",
588 "netslice_instances": {
589 "METHODS": ("GET", "POST"),
590 "ROLE_PERMISSION": "slice_instances:",
592 "METHODS": ("GET", "DELETE"),
593 "ROLE_PERMISSION": "slice_instances:id:",
595 "METHODS": ("POST",),
596 "ROLE_PERMISSION": "slice_instances:id:terminate:",
599 "METHODS": ("POST",),
600 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
603 "METHODS": ("POST",),
604 "ROLE_PERMISSION": "slice_instances:id:action:",
610 "ROLE_PERMISSION": "slice_instances:opps:",
613 "ROLE_PERMISSION": "slice_instances:opps:id:",
625 "ROLE_PERMISSION": "reports:id:",
635 "METHODS": ("GET", "PATCH"),
636 "ROLE_PERMISSION": "alarms:",
638 "METHODS": ("GET", "PATCH"),
639 "ROLE_PERMISSION": "alarms:id:",
647 class NbiException(Exception):
648 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
649 Exception.__init
__(self
, message
)
650 self
.http_code
= http_code
653 class Server(object):
655 # to decode bytes to str
656 reader
= getreader("utf-8")
660 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
661 self
.engine
= Engine(self
.authenticator
)
663 def _format_in(self
, kwargs
):
664 error_text
= "" # error_text must be initialized outside try
667 if cherrypy
.request
.body
.length
:
668 error_text
= "Invalid input format "
670 if "Content-Type" in cherrypy
.request
.headers
:
671 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
672 error_text
= "Invalid json format "
673 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
674 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
675 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
676 error_text
= "Invalid yaml format "
677 indata
= yaml
.safe_load(cherrypy
.request
.body
)
678 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
680 "application/binary" in cherrypy
.request
.headers
["Content-Type"]
681 or "application/gzip"
682 in cherrypy
.request
.headers
["Content-Type"]
683 or "application/zip" in cherrypy
.request
.headers
["Content-Type"]
684 or "text/plain" in cherrypy
.request
.headers
["Content-Type"]
686 indata
= cherrypy
.request
.body
# .read()
688 "multipart/form-data"
689 in cherrypy
.request
.headers
["Content-Type"]
691 if "descriptor_file" in kwargs
:
692 filecontent
= kwargs
.pop("descriptor_file")
693 if not filecontent
.file:
695 "empty file or content", HTTPStatus
.BAD_REQUEST
697 indata
= filecontent
.file # .read()
698 if filecontent
.content_type
.value
:
699 cherrypy
.request
.headers
[
701 ] = filecontent
.content_type
.value
703 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
704 # "Only 'Content-Type' of type 'application/json' or
705 # 'application/yaml' for input format are available")
706 error_text
= "Invalid yaml format "
707 indata
= yaml
.safe_load(cherrypy
.request
.body
)
708 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
710 error_text
= "Invalid yaml format "
711 indata
= yaml
.safe_load(cherrypy
.request
.body
)
712 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
717 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
720 for k
, v
in kwargs
.items():
721 if isinstance(v
, str):
726 kwargs
[k
] = yaml
.safe_load(v
)
732 or k
.endswith(".gte")
733 or k
.endswith(".lte")
742 elif v
.find(",") > 0:
743 kwargs
[k
] = v
.split(",")
744 elif isinstance(v
, (list, tuple)):
745 for index
in range(0, len(v
)):
750 v
[index
] = yaml
.safe_load(v
[index
])
755 except (ValueError, yaml
.YAMLError
) as exc
:
756 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
757 except KeyError as exc
:
759 "Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
761 except Exception as exc
:
762 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
765 def _format_out(data
, token_info
=None, _format
=None):
767 return string of dictionary data according to requested json, yaml, xml. By default json
768 :param data: response to be sent. Can be a dict, text or file
769 :param token_info: Contains among other username and project
770 :param _format: The format to be set as Content-Type if data is a file
773 accept
= cherrypy
.request
.headers
.get("Accept")
775 if accept
and "text/html" in accept
:
777 data
, cherrypy
.request
, cherrypy
.response
, token_info
779 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
781 elif hasattr(data
, "read"): # file object
783 cherrypy
.response
.headers
["Content-Type"] = _format
784 elif "b" in data
.mode
: # binariy asssumig zip
785 cherrypy
.response
.headers
["Content-Type"] = "application/zip"
787 cherrypy
.response
.headers
["Content-Type"] = "text/plain"
788 # TODO check that cherrypy close file. If not implement pending things to close per thread next
791 if "text/html" in accept
:
793 data
, cherrypy
.request
, cherrypy
.response
, token_info
795 elif "application/yaml" in accept
or "*/*" in accept
:
797 elif "application/json" in accept
or (
798 cherrypy
.response
.status
and cherrypy
.response
.status
>= 300
800 cherrypy
.response
.headers
[
802 ] = "application/json; charset=utf-8"
803 a
= json
.dumps(data
, indent
=4) + "\n"
804 return a
.encode("utf8")
805 cherrypy
.response
.headers
["Content-Type"] = "application/yaml"
806 return yaml
.safe_dump(
810 default_flow_style
=False,
814 ) # , canonical=True, default_style='"'
817 def index(self
, *args
, **kwargs
):
820 if cherrypy
.request
.method
== "GET":
821 token_info
= self
.authenticator
.authorize()
822 outdata
= token_info
# Home page
824 raise cherrypy
.HTTPError(
825 HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
826 "Method {} not allowed for tokens".format(cherrypy
.request
.method
),
829 return self
._format
_out
(outdata
, token_info
)
831 except (EngineException
, AuthException
) as e
:
832 # cherrypy.log("index Exception {}".format(e))
833 cherrypy
.response
.status
= e
.http_code
.value
834 return self
._format
_out
("Welcome to OSM!", token_info
)
837 def version(self
, *args
, **kwargs
):
838 # TODO consider to remove and provide version using the static version file
840 if cherrypy
.request
.method
!= "GET":
842 "Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
846 "Invalid URL or query string for version",
847 HTTPStatus
.METHOD_NOT_ALLOWED
,
849 # TODO include version of other modules, pick up from some kafka admin message
850 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
851 return self
._format
_out
(osm_nbi_version
)
852 except NbiException
as e
:
853 cherrypy
.response
.status
= e
.http_code
.value
855 "code": e
.http_code
.name
,
856 "status": e
.http_code
.value
,
859 return self
._format
_out
(problem_details
, None)
864 "user_domain_name": cherrypy
.tree
.apps
["/osm"]
865 .config
["authentication"]
866 .get("user_domain_name"),
867 "project_domain_name": cherrypy
.tree
.apps
["/osm"]
868 .config
["authentication"]
869 .get("project_domain_name"),
871 return self
._format
_out
(domains
)
872 except NbiException
as e
:
873 cherrypy
.response
.status
= e
.http_code
.value
875 "code": e
.http_code
.name
,
876 "status": e
.http_code
.value
,
879 return self
._format
_out
(problem_details
, None)
882 def _format_login(token_info
):
884 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
886 :param token_info: Dictionary with token content
889 cherrypy
.request
.login
= token_info
.get("username", "-")
890 if token_info
.get("project_name"):
891 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
892 if token_info
.get("id"):
893 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
895 # NS Fault Management
907 if topic
== "alarms":
909 method
= cherrypy
.request
.method
910 role_permission
= self
._check
_valid
_url
_method
(
911 method
, "nsfm", version
, topic
, None, None, *args
913 query_string_operations
= self
._extract
_query
_string
_operations
(
917 self
.authenticator
.authorize(
918 role_permission
, query_string_operations
, None
921 # to handle get request
922 if cherrypy
.request
.method
== "GET":
923 # if request is on basis of uuid
924 if uuid
and uuid
!= "None":
926 alarm
= self
.engine
.db
.get_one("alarms", {"uuid": uuid
})
927 alarm_action
= self
.engine
.db
.get_one(
928 "alarms_action", {"uuid": uuid
}
930 alarm
.update(alarm_action
)
931 vnf
= self
.engine
.db
.get_one(
932 "vnfrs", {"nsr-id-ref": alarm
["tags"]["ns_id"]}
934 alarm
["vnf-id"] = vnf
["_id"]
935 return self
._format
_out
(str(alarm
))
937 return self
._format
_out
("Please provide valid alarm uuid")
938 elif ns_id
and ns_id
!= "None":
939 # if request is on basis of ns_id
941 alarms
= self
.engine
.db
.get_list(
942 "alarms", {"tags.ns_id": ns_id
}
945 alarm_action
= self
.engine
.db
.get_one(
946 "alarms_action", {"uuid": alarm
["uuid"]}
948 alarm
.update(alarm_action
)
949 return self
._format
_out
(str(alarms
))
951 return self
._format
_out
("Please provide valid ns id")
953 # to return only alarm which are related to given project
954 project
= self
.engine
.db
.get_one(
955 "projects", {"name": project_name
}
957 project_id
= project
.get("_id")
958 ns_list
= self
.engine
.db
.get_list(
959 "nsrs", {"_admin.projects_read": project_id
}
963 ns_ids
.append(ns
.get("_id"))
964 alarms
= self
.engine
.db
.get_list("alarms")
968 if alarm
["tags"]["ns_id"] in ns_ids
970 for alrm
in alarm_list
:
971 action
= self
.engine
.db
.get_one(
972 "alarms_action", {"uuid": alrm
.get("uuid")}
975 return self
._format
_out
(str(alarm_list
))
976 # to handle patch request for alarm update
977 elif cherrypy
.request
.method
== "PATCH":
978 data
= yaml
.safe_load(cherrypy
.request
.body
)
980 # check if uuid is valid
981 self
.engine
.db
.get_one("alarms", {"uuid": data
.get("uuid")})
983 return self
._format
_out
("Please provide valid alarm uuid.")
984 if data
.get("is_enable") is not None:
985 if data
.get("is_enable"):
988 alarm_status
= "disabled"
989 self
.engine
.db
.set_one(
991 {"uuid": data
.get("uuid")},
992 {"alarm_status": alarm_status
},
995 self
.engine
.db
.set_one(
997 {"uuid": data
.get("uuid")},
998 {"threshold": data
.get("threshold")},
1000 return self
._format
_out
("Alarm updated")
1001 except Exception as e
:
1015 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1016 http_code_name
= e
.http_code
.name
1017 cherrypy
.log("Exception {}".format(e
))
1020 cherrypy
.response
.status
1021 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1022 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1023 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1025 "code": http_code_name
,
1026 "status": http_code_value
,
1029 return self
._format
_out
(problem_details
)
1032 def token(self
, method
, token_id
=None, kwargs
=None):
1034 # self.engine.load_dbase(cherrypy.request.app.config)
1035 indata
= self
._format
_in
(kwargs
)
1036 if not isinstance(indata
, dict):
1038 "Expected application/yaml or application/json Content-Type",
1039 HTTPStatus
.BAD_REQUEST
,
1043 token_info
= self
.authenticator
.authorize()
1045 self
._format
_login
(token_info
)
1047 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
1049 outdata
= self
.authenticator
.get_token_list(token_info
)
1050 elif method
== "POST":
1052 token_info
= self
.authenticator
.authorize()
1056 indata
.update(kwargs
)
1057 # This is needed to log the user when authentication fails
1058 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
1059 outdata
= token_info
= self
.authenticator
.new_token(
1060 token_info
, indata
, cherrypy
.request
.remote
1062 cherrypy
.session
["Authorization"] = outdata
["_id"] # pylint: disable=E1101
1063 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
1065 self
._format
_login
(token_info
)
1066 # password expiry check
1067 if self
.authenticator
.check_password_expiry(outdata
):
1069 "id": outdata
["id"],
1070 "message": "change_password",
1071 "user_id": outdata
["user_id"],
1073 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1074 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1078 "name": "User Login",
1079 "sourceUserName": token_info
.get("username"),
1080 "message": "User Logged In, Project={} Outcome=Success".format(
1081 token_info
.get("project_name")
1085 cherrypy
.log("{}".format(cef_logger
))
1086 elif method
== "DELETE":
1087 if not token_id
and "id" in kwargs
:
1088 token_id
= kwargs
["id"]
1090 token_info
= self
.authenticator
.authorize()
1092 self
._format
_login
(token_info
)
1093 token_id
= token_info
["_id"]
1094 if current_backend
!= "keystone":
1095 token_details
= self
.engine
.db
.get_one("tokens", {"_id": token_id
})
1096 current_user
= token_details
.get("username")
1097 current_project
= token_details
.get("project_name")
1099 current_user
= "keystone backend"
1100 current_project
= "keystone backend"
1101 outdata
= self
.authenticator
.del_token(token_id
)
1103 cherrypy
.session
["Authorization"] = "logout" # pylint: disable=E1101
1107 "name": "User Logout",
1108 "sourceUserName": current_user
,
1109 "message": "User Logged Out, Project={} Outcome=Success".format(
1114 cherrypy
.log("{}".format(cef_logger
))
1115 # cherrypy.response.cookie["Authorization"] = token_id
1116 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1119 "Method {} not allowed for token".format(method
),
1120 HTTPStatus
.METHOD_NOT_ALLOWED
,
1122 return self
._format
_out
(outdata
, token_info
)
1125 def test(self
, *args
, **kwargs
):
1126 if not cherrypy
.config
.get("server.enable_test") or (
1127 isinstance(cherrypy
.config
["server.enable_test"], str)
1128 and cherrypy
.config
["server.enable_test"].lower() == "false"
1130 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
1131 return "test URL is disabled"
1133 if args
and args
[0] == "help":
1135 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1136 "sleep/<time>\nmessage/topic\n</pre></html>"
1139 elif args
and args
[0] == "init":
1141 # self.engine.load_dbase(cherrypy.request.app.config)
1142 pid
= self
.authenticator
.create_admin_project()
1143 self
.authenticator
.create_admin_user(pid
)
1144 return "Done. User 'admin', password 'admin' created"
1146 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1147 return self
._format
_out
("Database already initialized")
1148 elif args
and args
[0] == "file":
1149 return cherrypy
.lib
.static
.serve_file(
1150 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1],
1154 elif args
and args
[0] == "file2":
1156 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1]
1158 f
= open(f_path
, "r")
1159 cherrypy
.response
.headers
["Content-type"] = "text/plain"
1162 elif len(args
) == 2 and args
[0] == "db-clear":
1163 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
1164 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
1165 elif len(args
) and args
[0] == "fs-clear":
1167 folders
= (args
[1],)
1169 folders
= self
.engine
.fs
.dir_ls(".")
1170 for folder
in folders
:
1171 self
.engine
.fs
.file_delete(folder
)
1172 return ",".join(folders
) + " folders deleted\n"
1173 elif args
and args
[0] == "login":
1174 if not cherrypy
.request
.headers
.get("Authorization"):
1175 cherrypy
.response
.headers
[
1177 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
1178 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1179 elif args
and args
[0] == "login2":
1180 if not cherrypy
.request
.headers
.get("Authorization"):
1181 cherrypy
.response
.headers
[
1183 ] = 'Bearer realm="Access to OSM site"'
1184 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1185 elif args
and args
[0] == "sleep":
1188 sleep_time
= int(args
[1])
1190 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1191 return self
._format
_out
("Database already initialized")
1192 thread_info
= cherrypy
.thread_data
1194 time
.sleep(sleep_time
)
1196 elif len(args
) >= 2 and args
[0] == "message":
1197 main_topic
= args
[1]
1198 return_text
= "<html><pre>{} ->\n".format(main_topic
)
1200 if cherrypy
.request
.method
== "POST":
1201 to_send
= yaml
.safe_load(cherrypy
.request
.body
)
1202 for k
, v
in to_send
.items():
1203 self
.engine
.msg
.write(main_topic
, k
, v
)
1204 return_text
+= " {}: {}\n".format(k
, v
)
1205 elif cherrypy
.request
.method
== "GET":
1206 for k
, v
in kwargs
.items():
1207 v_dict
= yaml
.safe_load(v
)
1208 self
.engine
.msg
.write(main_topic
, k
, v_dict
)
1209 return_text
+= " {}: {}\n".format(k
, v_dict
)
1210 except Exception as e
:
1211 return_text
+= "Error: " + str(e
)
1212 return_text
+= "</pre></html>\n"
1216 "<html><pre>\nheaders:\n args: {}\n".format(args
)
1217 + " kwargs: {}\n".format(kwargs
)
1218 + " headers: {}\n".format(cherrypy
.request
.headers
)
1219 + " path_info: {}\n".format(cherrypy
.request
.path_info
)
1220 + " query_string: {}\n".format(cherrypy
.request
.query_string
)
1221 + " session: {}\n".format(cherrypy
.session
) # pylint: disable=E1101
1222 + " cookie: {}\n".format(cherrypy
.request
.cookie
)
1223 + " method: {}\n".format(cherrypy
.request
.method
)
1224 + " session: {}\n".format(
1225 cherrypy
.session
.get("fieldname") # pylint: disable=E1101
1229 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
1230 if cherrypy
.request
.body
.length
:
1231 return_text
+= " content: {}\n".format(
1233 cherrypy
.request
.body
.read(
1234 int(cherrypy
.request
.headers
.get("Content-Length", 0))
1239 return_text
+= "thread: {}\n".format(thread_info
)
1240 return_text
+= "</pre></html>"
1244 def _check_valid_url_method(method
, *args
):
1247 "URL must contain at least 'main_topic/version/topic'",
1248 HTTPStatus
.METHOD_NOT_ALLOWED
,
1251 reference
= valid_url_methods
1255 if not isinstance(reference
, dict):
1257 "URL contains unexpected extra items '{}'".format(arg
),
1258 HTTPStatus
.METHOD_NOT_ALLOWED
,
1261 if arg
in reference
:
1262 reference
= reference
[arg
]
1263 elif "<ID>" in reference
:
1264 reference
= reference
["<ID>"]
1265 elif "*" in reference
:
1266 # if there is content
1268 reference
= reference
["*"]
1272 "Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
1274 if "TODO" in reference
and method
in reference
["TODO"]:
1276 "Method {} not supported yet for this URL".format(method
),
1277 HTTPStatus
.NOT_IMPLEMENTED
,
1279 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
1281 "Method {} not supported for this URL".format(method
),
1282 HTTPStatus
.METHOD_NOT_ALLOWED
,
1284 return reference
["ROLE_PERMISSION"] + method
.lower()
1287 def _set_location_header(main_topic
, version
, topic
, id):
1289 Insert response header Location with the URL of created item base on URL params
1296 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1297 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(
1298 main_topic
, version
, topic
, id
1303 def _extract_query_string_operations(kwargs
, method
):
1309 query_string_operations
= []
1311 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1312 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
1313 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
1314 return query_string_operations
1317 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
1319 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1320 Check that users has rights to use them and returs the admin_query
1321 :param token_info: token_info rights obtained by token
1322 :param kwargs: query string input.
1323 :param method: http method: GET, POSST, PUT, ...
1325 :return: admin_query dictionary with keys:
1326 public: True, False or None
1327 force: True or False
1328 project_id: tuple with projects used for accessing an element
1329 set_project: tuple with projects that a created element will belong to
1330 method: show, list, delete, write
1334 "project_id": (token_info
["project_id"],),
1335 "username": token_info
["username"],
1336 "admin": token_info
["admin"],
1338 "allow_show_user_project_role": token_info
["allow_show_user_project_role"],
1342 if "FORCE" in kwargs
:
1344 kwargs
["FORCE"].lower() != "false"
1345 ): # if None or True set force to True
1346 admin_query
["force"] = True
1349 if "PUBLIC" in kwargs
:
1351 kwargs
["PUBLIC"].lower() != "false"
1352 ): # if None or True set public to True
1353 admin_query
["public"] = True
1355 admin_query
["public"] = False
1356 del kwargs
["PUBLIC"]
1358 if "ADMIN" in kwargs
:
1359 behave_as
= kwargs
.pop("ADMIN")
1360 if behave_as
.lower() != "false":
1361 if not token_info
["admin"]:
1363 "Only admin projects can use 'ADMIN' query string",
1364 HTTPStatus
.UNAUTHORIZED
,
1367 not behave_as
or behave_as
.lower() == "true"
1368 ): # convert True, None to empty list
1369 admin_query
["project_id"] = ()
1370 elif isinstance(behave_as
, (list, tuple)):
1371 admin_query
["project_id"] = behave_as
1372 else: # isinstance(behave_as, str)
1373 admin_query
["project_id"] = (behave_as
,)
1374 if "SET_PROJECT" in kwargs
:
1375 set_project
= kwargs
.pop("SET_PROJECT")
1377 admin_query
["set_project"] = list(admin_query
["project_id"])
1379 if isinstance(set_project
, str):
1380 set_project
= (set_project
,)
1381 if admin_query
["project_id"]:
1382 for p
in set_project
:
1383 if p
not in admin_query
["project_id"]:
1385 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1386 "'ADMIN='{p}'".format(p
=p
),
1387 HTTPStatus
.UNAUTHORIZED
,
1389 admin_query
["set_project"] = set_project
1392 # if "PROJECT_READ" in kwargs:
1393 # admin_query["project"] = kwargs.pop("project")
1394 # if admin_query["project"] == token_info["project_id"]:
1397 admin_query
["method"] = "show"
1399 admin_query
["method"] = "list"
1400 elif method
== "DELETE":
1401 admin_query
["method"] = "delete"
1403 admin_query
["method"] = "write"
1423 engine_session
= None
1428 "DELETE": "Deleting",
1430 "PATCH": "Updating",
1433 if not main_topic
or not version
or not topic
:
1435 "URL must contain at least 'main_topic/version/topic'",
1436 HTTPStatus
.METHOD_NOT_ALLOWED
,
1438 if main_topic
not in (
1450 "URL main_topic '{}' not supported".format(main_topic
),
1451 HTTPStatus
.METHOD_NOT_ALLOWED
,
1455 "URL version '{}' not supported".format(version
),
1456 HTTPStatus
.METHOD_NOT_ALLOWED
,
1463 and "METHOD" in kwargs
1464 and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1466 method
= kwargs
.pop("METHOD")
1468 method
= cherrypy
.request
.method
1470 role_permission
= self
._check
_valid
_url
_method
(
1471 method
, main_topic
, version
, topic
, _id
, item
, *args
1473 query_string_operations
= self
._extract
_query
_string
_operations
(
1476 if main_topic
== "admin" and topic
== "tokens":
1477 return self
.token(method
, _id
, kwargs
)
1478 token_info
= self
.authenticator
.authorize(
1479 role_permission
, query_string_operations
, _id
1481 if main_topic
== "admin" and topic
== "domains":
1482 return self
.domain()
1483 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
1484 indata
= self
._format
_in
(kwargs
)
1485 engine_topic
= topic
1487 if item
and topic
!= "pm_jobs":
1490 if main_topic
== "nsd":
1491 engine_topic
= "nsds"
1492 elif main_topic
== "vnfpkgm":
1493 engine_topic
= "vnfds"
1494 if topic
== "vnfpkg_op_occs":
1495 engine_topic
= "vnfpkgops"
1496 if topic
== "vnf_packages" and item
== "action":
1497 engine_topic
= "vnfpkgops"
1498 elif main_topic
== "nslcm":
1499 engine_topic
= "nsrs"
1500 if topic
== "ns_lcm_op_occs":
1501 engine_topic
= "nslcmops"
1502 if topic
== "vnfrs" or topic
== "vnf_instances":
1503 engine_topic
= "vnfrs"
1504 elif main_topic
== "vnflcm":
1505 if topic
== "vnf_lcm_op_occs":
1506 engine_topic
= "vnflcmops"
1507 elif main_topic
== "nst":
1508 engine_topic
= "nsts"
1509 elif main_topic
== "nsilcm":
1510 engine_topic
= "nsis"
1511 if topic
== "nsi_lcm_op_occs":
1512 engine_topic
= "nsilcmops"
1513 elif main_topic
== "pdu":
1514 engine_topic
= "pdus"
1516 engine_topic
== "vims"
1517 ): # TODO this is for backward compatibility, it will be removed in the future
1518 engine_topic
= "vim_accounts"
1520 if topic
== "subscriptions":
1521 engine_topic
= main_topic
+ "_" + topic
1533 if item
in ("vnfd", "nsd", "nst"):
1534 path
= "$DESCRIPTOR"
1537 elif item
== "artifacts":
1541 file, _format
= self
.engine
.get_file(
1546 cherrypy
.request
.headers
.get("Accept"),
1550 outdata
= self
.engine
.get_item_list(
1551 engine_session
, engine_topic
, kwargs
, api_req
=True
1554 if item
== "reports":
1555 # TODO check that project_id (_id in this context) has permissions
1558 if "vcaStatusRefresh" in kwargs
:
1559 filter_q
= {"vcaStatusRefresh": kwargs
["vcaStatusRefresh"]}
1560 outdata
= self
.engine
.get_item(
1561 engine_session
, engine_topic
, _id
, filter_q
, True
1564 elif method
== "POST":
1565 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1567 "ns_descriptors_content",
1568 "vnf_packages_content",
1569 "netslice_templates_content",
1571 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1573 _id
, _
= self
.engine
.new_item(
1579 cherrypy
.request
.headers
,
1581 completed
= self
.engine
.upload_content(
1587 cherrypy
.request
.headers
,
1590 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1592 cherrypy
.response
.headers
["Transaction-Id"] = _id
1593 outdata
= {"id": _id
}
1594 elif topic
== "ns_instances_content":
1596 _id
, _
= self
.engine
.new_item(
1597 rollback
, engine_session
, engine_topic
, indata
, kwargs
1600 indata
["lcmOperationType"] = "instantiate"
1601 indata
["nsInstanceId"] = _id
1602 nslcmop_id
, _
= self
.engine
.new_item(
1603 rollback
, engine_session
, "nslcmops", indata
, None
1605 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1606 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1607 elif topic
== "ns_instances" and item
:
1608 indata
["lcmOperationType"] = item
1609 indata
["nsInstanceId"] = _id
1610 _id
, _
= self
.engine
.new_item(
1611 rollback
, engine_session
, "nslcmops", indata
, kwargs
1613 self
._set
_location
_header
(
1614 main_topic
, version
, "ns_lcm_op_occs", _id
1616 outdata
= {"id": _id
}
1617 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1618 elif topic
== "netslice_instances_content":
1619 # creates NetSlice_Instance_record (NSIR)
1620 _id
, _
= self
.engine
.new_item(
1621 rollback
, engine_session
, engine_topic
, indata
, kwargs
1623 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1624 indata
["lcmOperationType"] = "instantiate"
1625 indata
["netsliceInstanceId"] = _id
1626 nsilcmop_id
, _
= self
.engine
.new_item(
1627 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1629 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1630 elif topic
== "netslice_instances" and item
:
1631 indata
["lcmOperationType"] = item
1632 indata
["netsliceInstanceId"] = _id
1633 _id
, _
= self
.engine
.new_item(
1634 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1636 self
._set
_location
_header
(
1637 main_topic
, version
, "nsi_lcm_op_occs", _id
1639 outdata
= {"id": _id
}
1640 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1641 elif topic
== "vnf_packages" and item
== "action":
1642 indata
["lcmOperationType"] = item
1643 indata
["vnfPkgId"] = _id
1644 _id
, _
= self
.engine
.new_item(
1645 rollback
, engine_session
, "vnfpkgops", indata
, kwargs
1647 self
._set
_location
_header
(
1648 main_topic
, version
, "vnfpkg_op_occs", _id
1650 outdata
= {"id": _id
}
1651 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1652 elif topic
== "subscriptions":
1653 _id
, _
= self
.engine
.new_item(
1654 rollback
, engine_session
, engine_topic
, indata
, kwargs
1656 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1658 link
["self"] = cherrypy
.response
.headers
["Location"]
1661 "filter": indata
["filter"],
1662 "callbackUri": indata
["CallbackUri"],
1665 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1666 elif topic
== "vnf_instances" and item
:
1667 indata
["lcmOperationType"] = item
1668 indata
["vnfInstanceId"] = _id
1669 _id
, _
= self
.engine
.new_item(
1670 rollback
, engine_session
, "vnflcmops", indata
, kwargs
1672 self
._set
_location
_header
(
1673 main_topic
, version
, "vnf_lcm_op_occs", _id
1675 outdata
= {"id": _id
}
1676 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1678 _id
, op_id
= self
.engine
.new_item(
1684 cherrypy
.request
.headers
,
1686 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1687 outdata
= {"id": _id
}
1689 outdata
["op_id"] = op_id
1690 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1691 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1693 elif method
== "DELETE":
1695 outdata
= self
.engine
.del_item_list(
1696 engine_session
, engine_topic
, kwargs
1698 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1699 else: # len(args) > 1
1700 # for NS NSI generate an operation
1702 if topic
== "ns_instances_content" and not engine_session
["force"]:
1704 "lcmOperationType": "terminate",
1705 "nsInstanceId": _id
,
1708 op_id
, _
= self
.engine
.new_item(
1709 rollback
, engine_session
, "nslcmops", nslcmop_desc
, kwargs
1712 outdata
= {"_id": op_id
}
1714 topic
== "netslice_instances_content"
1715 and not engine_session
["force"]
1718 "lcmOperationType": "terminate",
1719 "netsliceInstanceId": _id
,
1722 op_id
, _
= self
.engine
.new_item(
1723 rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None
1726 outdata
= {"_id": op_id
}
1727 # if there is not any deletion in process, delete
1729 op_id
= self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1731 outdata
= {"op_id": op_id
}
1732 cherrypy
.response
.status
= (
1733 HTTPStatus
.ACCEPTED
.value
1735 else HTTPStatus
.NO_CONTENT
.value
1738 elif method
in ("PUT", "PATCH"):
1740 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1742 "Nothing to update. Provide payload and/or query string",
1743 HTTPStatus
.BAD_REQUEST
,
1746 item
in ("nsd_content", "package_content", "nst_content")
1749 completed
= self
.engine
.upload_content(
1755 cherrypy
.request
.headers
,
1758 cherrypy
.response
.headers
["Transaction-Id"] = id
1760 op_id
= self
.engine
.edit_item(
1761 engine_session
, engine_topic
, _id
, indata
, kwargs
1765 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1766 outdata
= {"op_id": op_id
}
1768 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1772 "Method {} not allowed".format(method
),
1773 HTTPStatus
.METHOD_NOT_ALLOWED
,
1776 # if Role information changes, it is needed to reload the information of roles
1777 if topic
== "roles" and method
!= "GET":
1778 self
.authenticator
.load_operation_to_allowed_roles()
1782 and method
== "DELETE"
1783 or topic
in ["users", "roles"]
1784 and method
in ["PUT", "PATCH", "DELETE"]
1786 self
.authenticator
.remove_token_from_cache()
1788 if item
is not None:
1792 "name": "User Operation",
1793 "sourceUserName": token_info
.get("username"),
1794 "message": "Performing {} operation on {} {}, Project={} Outcome=Success".format(
1798 token_info
.get("project_name"),
1802 cherrypy
.log("{}".format(cef_logger
))
1807 "name": "User Operation",
1808 "sourceUserName": token_info
.get("username"),
1809 "message": "{} {} {}, Project={} Outcome=Success".format(
1810 log_mapping
[method
],
1813 token_info
.get("project_name"),
1817 cherrypy
.log("{}".format(cef_logger
))
1818 return self
._format
_out
(outdata
, token_info
, _format
)
1819 except Exception as e
:
1833 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1834 http_code_name
= e
.http_code
.name
1835 cherrypy
.log("Exception {}".format(e
))
1838 cherrypy
.response
.status
1839 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1840 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1841 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1842 if hasattr(outdata
, "close"): # is an open file
1846 for rollback_item
in rollback
:
1848 if rollback_item
.get("operation") == "set":
1849 self
.engine
.db
.set_one(
1850 rollback_item
["topic"],
1851 {"_id": rollback_item
["_id"]},
1852 rollback_item
["content"],
1853 fail_on_empty
=False,
1855 elif rollback_item
.get("operation") == "del_list":
1856 self
.engine
.db
.del_list(
1857 rollback_item
["topic"],
1858 rollback_item
["filter"],
1861 self
.engine
.db
.del_one(
1862 rollback_item
["topic"],
1863 {"_id": rollback_item
["_id"]},
1864 fail_on_empty
=False,
1866 except Exception as e2
:
1867 rollback_error_text
= "Rollback Exception {}: {}".format(
1870 cherrypy
.log(rollback_error_text
)
1871 error_text
+= ". " + rollback_error_text
1872 # if isinstance(e, MsgException):
1873 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1874 # engine_topic[:-1], method, error_text)
1876 "code": http_code_name
,
1877 "status": http_code_value
,
1878 "detail": error_text
,
1880 if item
is not None and token_info
is not None:
1884 "name": "User Operation",
1885 "sourceUserName": token_info
.get("username", None),
1886 "message": "Performing {} operation on {} {}, Project={} Outcome=Failure".format(
1890 token_info
.get("project_name", None),
1895 cherrypy
.log("{}".format(cef_logger
))
1896 elif token_info
is not None:
1900 "name": "User Operation",
1901 "sourceUserName": token_info
.get("username", None),
1902 "message": "{} {} {}, Project={} Outcome=Failure".format(
1906 token_info
.get("project_name", None),
1911 cherrypy
.log("{}".format(cef_logger
))
1912 return self
._format
_out
(problem_details
, token_info
)
1913 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1916 self
._format
_login
(token_info
)
1917 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1918 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1919 if outdata
.get(logging_id
):
1920 cherrypy
.request
.login
+= ";{}={}".format(
1921 logging_id
, outdata
[logging_id
][:36]
1925 def _start_service():
1927 Callback function called when cherrypy.engine starts
1928 Override configuration with env variables
1929 Set database, storage, message configuration
1930 Init database with admin/admin user password
1933 global subscription_thread
1935 global current_backend
1936 cherrypy
.log
.error("Starting osm_nbi")
1937 # update general cherrypy configuration
1940 engine_config
= cherrypy
.tree
.apps
["/osm"].config
1941 for k
, v
in environ
.items():
1942 if k
== "OSMNBI_USER_MANAGEMENT":
1943 feature_state
= eval(v
.title())
1944 engine_config
["authentication"]["user_management"] = feature_state
1945 elif k
== "OSMNBI_PWD_EXPIRE_DAYS":
1946 pwd_expire_days
= int(v
)
1947 engine_config
["authentication"]["pwd_expire_days"] = pwd_expire_days
1948 elif k
== "OSMNBI_MAX_PWD_ATTEMPT":
1949 max_pwd_attempt
= int(v
)
1950 engine_config
["authentication"]["max_pwd_attempt"] = max_pwd_attempt
1951 elif k
== "OSMNBI_ACCOUNT_EXPIRE_DAYS":
1952 account_expire_days
= int(v
)
1953 engine_config
["authentication"]["account_expire_days"] = account_expire_days
1954 if not k
.startswith("OSMNBI_"):
1956 k1
, _
, k2
= k
[7:].lower().partition("_")
1960 # update static configuration
1961 if k
== "OSMNBI_STATIC_DIR":
1962 engine_config
["/static"]["tools.staticdir.dir"] = v
1963 engine_config
["/static"]["tools.staticdir.on"] = True
1964 elif k
== "OSMNBI_SOCKET_PORT" or k
== "OSMNBI_SERVER_PORT":
1965 update_dict
["server.socket_port"] = int(v
)
1966 elif k
== "OSMNBI_SOCKET_HOST" or k
== "OSMNBI_SERVER_HOST":
1967 update_dict
["server.socket_host"] = v
1968 elif k1
in ("server", "test", "auth", "log"):
1969 update_dict
[k1
+ "." + k2
] = v
1970 elif k1
in ("message", "database", "storage", "authentication"):
1971 # k2 = k2.replace('_', '.')
1972 if k2
in ("port", "db_port"):
1973 engine_config
[k1
][k2
] = int(v
)
1975 engine_config
[k1
][k2
] = v
1977 except ValueError as e
:
1978 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1979 except Exception as e
:
1981 "WARNING: skipping environ '{}' on exception '{}'".format(k
, e
)
1985 cherrypy
.config
.update(update_dict
)
1986 engine_config
["global"].update(update_dict
)
1989 log_format_simple
= (
1990 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1992 log_formatter_simple
= logging
.Formatter(
1993 log_format_simple
, datefmt
="%Y-%m-%dT%H:%M:%S"
1995 logger_server
= logging
.getLogger("cherrypy.error")
1996 logger_access
= logging
.getLogger("cherrypy.access")
1997 logger_cherry
= logging
.getLogger("cherrypy")
1998 logger_nbi
= logging
.getLogger("nbi")
2000 if "log.file" in engine_config
["global"]:
2001 file_handler
= logging
.handlers
.RotatingFileHandler(
2002 engine_config
["global"]["log.file"], maxBytes
=100e6
, backupCount
=9, delay
=0
2004 file_handler
.setFormatter(log_formatter_simple
)
2005 logger_cherry
.addHandler(file_handler
)
2006 logger_nbi
.addHandler(file_handler
)
2007 # log always to standard output
2008 for format_
, logger
in {
2009 "nbi.server %(filename)s:%(lineno)s": logger_server
,
2010 "nbi.access %(filename)s:%(lineno)s": logger_access
,
2011 "%(name)s %(filename)s:%(lineno)s": logger_nbi
,
2013 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
2014 log_formatter_cherry
= logging
.Formatter(
2015 log_format_cherry
, datefmt
="%Y-%m-%dT%H:%M:%S"
2017 str_handler
= logging
.StreamHandler()
2018 str_handler
.setFormatter(log_formatter_cherry
)
2019 logger
.addHandler(str_handler
)
2021 if engine_config
["global"].get("log.level"):
2022 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
2023 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
2025 # logging other modules
2026 for k1
, logname
in {
2027 "message": "nbi.msg",
2028 "database": "nbi.db",
2029 "storage": "nbi.fs",
2031 engine_config
[k1
]["logger_name"] = logname
2032 logger_module
= logging
.getLogger(logname
)
2033 if "logfile" in engine_config
[k1
]:
2034 file_handler
= logging
.handlers
.RotatingFileHandler(
2035 engine_config
[k1
]["logfile"], maxBytes
=100e6
, backupCount
=9, delay
=0
2037 file_handler
.setFormatter(log_formatter_simple
)
2038 logger_module
.addHandler(file_handler
)
2039 if "loglevel" in engine_config
[k1
]:
2040 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
2041 # TODO add more entries, e.g.: storage
2042 cherrypy
.tree
.apps
["/osm"].root
.engine
.start(engine_config
)
2043 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.start(engine_config
)
2044 cherrypy
.tree
.apps
["/osm"].root
.engine
.init_db(target_version
=database_version
)
2045 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.init_db(
2046 target_version
=auth_database_version
2049 cef_logger
= cef_event_builder(engine_config
["authentication"])
2051 # start subscriptions thread:
2052 subscription_thread
= SubscriptionThread(
2053 config
=engine_config
, engine
=nbi_server
.engine
2055 subscription_thread
.start()
2056 # Do not capture except SubscriptionException
2058 backend
= engine_config
["authentication"]["backend"]
2059 current_backend
= backend
2061 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
2062 nbi_version
, nbi_version_date
, backend
2067 def _stop_service():
2069 Callback function called when cherrypy.engine stops
2070 TODO: Ending database connections.
2072 global subscription_thread
2073 if subscription_thread
:
2074 subscription_thread
.terminate()
2075 subscription_thread
= None
2076 cherrypy
.tree
.apps
["/osm"].root
.engine
.stop()
2077 cherrypy
.log
.error("Stopping osm_nbi")
2080 def nbi(config_file
):
2084 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
2085 # 'tools.sessions.on': True,
2086 # 'tools.response_headers.on': True,
2087 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
2090 # cherrypy.Server.ssl_module = 'builtin'
2091 # cherrypy.Server.ssl_certificate = "http/cert.pem"
2092 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
2093 # cherrypy.Server.thread_pool = 10
2094 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
2096 # cherrypy.config.update({'tools.auth_basic.on': True,
2097 # 'tools.auth_basic.realm': 'localhost',
2098 # 'tools.auth_basic.checkpassword': validate_password})
2099 nbi_server
= Server()
2100 cherrypy
.engine
.subscribe("start", _start_service
)
2101 cherrypy
.engine
.subscribe("stop", _stop_service
)
2102 cherrypy
.quickstart(nbi_server
, "/osm", config_file
)
2107 """Usage: {} [options]
2108 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
2109 -h|--help: shows this help
2114 # --log-socket-host HOST: send logs to this host")
2115 # --log-socket-port PORT: send logs using this port (default: 9022)")
2118 if __name__
== "__main__":
2120 # load parameters and configuration
2121 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
2122 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
2125 if o
in ("-h", "--help"):
2128 elif o
in ("-c", "--config"):
2130 # elif o == "--log-socket-port":
2131 # log_socket_port = a
2132 # elif o == "--log-socket-host":
2133 # log_socket_host = a
2134 # elif o == "--log-file":
2137 assert False, "Unhandled option"
2139 if not path
.isfile(config_file
):
2141 "configuration file '{}' that not exist".format(config_file
),
2146 for config_file
in (
2147 __file__
[: __file__
.rfind(".")] + ".cfg",
2151 if path
.isfile(config_file
):
2155 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
2160 except getopt
.GetoptError
as e
:
2161 print(str(e
), file=sys
.stderr
)