2 # -*- coding: utf-8 -*-
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
21 import osm_nbi
.html_out
as html
23 import logging
.handlers
27 from osm_nbi
.authconn
import AuthException
, AuthconnException
28 from osm_nbi
.auth
import Authenticator
29 from osm_nbi
.engine
import Engine
, EngineException
30 from osm_nbi
.subscriptions
import SubscriptionThread
31 from osm_nbi
.validation
import ValidationError
32 from osm_common
.dbbase
import DbException
33 from osm_common
.fsbase
import FsException
34 from osm_common
.msgbase
import MsgException
35 from http
import HTTPStatus
36 from codecs
import getreader
37 from os
import environ
, path
38 from osm_nbi
import version
as nbi_version
, version_date
as nbi_version_date
40 __author__
= "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
42 __version__
= "0.1.3" # file version, not NBI version
43 version_date
= "Aug 2019"
45 database_version
= "1.2"
46 auth_database_version
= "1.0"
47 nbi_server
= None # instance of Server class
48 subscription_thread
= None # instance of SubscriptionThread class
51 North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
52 URL: /osm GET POST PUT DELETE PATCH
54 /ns_descriptors_content O O
60 /artifacts[/<artifactPath>] O
68 /vnf_packages_content O O
72 /package_content O5 O5
75 /artifacts[/<artifactPath>] O5
80 /ns_instances_content O O
94 /vnf_instances (also vnfrs for compatibility) O
110 /vim_accounts (also vims for compatibility) O O
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": "paas:",
297 "METHODS": ("GET", "DELETE", "PATCH"),
298 "ROLE_PERMISSION": "paas:id:",
302 "METHODS": ("GET", "POST"),
303 "ROLE_PERMISSION": "k8srepos:",
305 "METHODS": ("GET", "DELETE"),
306 "ROLE_PERMISSION": "k8srepos:id:",
310 "METHODS": ("GET", "POST"),
311 "ROLE_PERMISSION": "osmrepos:",
313 "METHODS": ("GET", "DELETE", "PATCH"),
314 "ROLE_PERMISSION": "osmrepos:id:",
319 "ROLE_PERMISSION": "domains:",
326 "METHODS": ("GET", "POST"),
327 "ROLE_PERMISSION": "pduds:",
329 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
330 "ROLE_PERMISSION": "pduds:id:",
337 "ns_descriptors_content": {
338 "METHODS": ("GET", "POST"),
339 "ROLE_PERMISSION": "nsds:",
341 "METHODS": ("GET", "PUT", "DELETE"),
342 "ROLE_PERMISSION": "nsds:id:",
346 "METHODS": ("GET", "POST"),
347 "ROLE_PERMISSION": "nsds:",
349 "METHODS": ("GET", "DELETE", "PATCH"),
350 "ROLE_PERMISSION": "nsds:id:",
352 "METHODS": ("GET", "PUT"),
353 "ROLE_PERMISSION": "nsds:id:content:",
356 "METHODS": ("GET",), # descriptor inside package
357 "ROLE_PERMISSION": "nsds:id:content:",
361 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
367 "TODO": ("GET", "POST"),
369 "TODO": ("GET", "DELETE", "PATCH"),
370 "pnfd_content": {"TODO": ("GET", "PUT")},
374 "TODO": ("GET", "POST"),
375 "<ID>": {"TODO": ("GET", "DELETE")},
381 "vnf_packages_content": {
382 "METHODS": ("GET", "POST"),
383 "ROLE_PERMISSION": "vnfds:",
385 "METHODS": ("GET", "PUT", "DELETE"),
386 "ROLE_PERMISSION": "vnfds:id:",
390 "METHODS": ("GET", "POST"),
391 "ROLE_PERMISSION": "vnfds:",
393 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
394 "ROLE_PERMISSION": "vnfds:id:",
396 "METHODS": ("GET", "PUT"), # package
397 "ROLE_PERMISSION": "vnfds:id:",
401 "ROLE_PERMISSION": "vnfds:id:upload:",
405 "METHODS": ("GET",), # descriptor inside package
406 "ROLE_PERMISSION": "vnfds:id:content:",
410 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
414 "METHODS": ("POST",),
415 "ROLE_PERMISSION": "vnfds:id:action:",
420 "TODO": ("GET", "POST"),
421 "<ID>": {"TODO": ("GET", "DELETE")},
425 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
426 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"},
432 "ns_instances_content": {
433 "METHODS": ("GET", "POST"),
434 "ROLE_PERMISSION": "ns_instances:",
436 "METHODS": ("GET", "DELETE"),
437 "ROLE_PERMISSION": "ns_instances:id:",
441 "METHODS": ("GET", "POST"),
442 "ROLE_PERMISSION": "ns_instances:",
444 "METHODS": ("GET", "DELETE"),
445 "ROLE_PERMISSION": "ns_instances:id:",
447 "METHODS": ("POST",),
448 "ROLE_PERMISSION": "ns_instances:id:heal:",
451 "METHODS": ("POST",),
452 "ROLE_PERMISSION": "ns_instances:id:scale:",
455 "METHODS": ("POST",),
456 "ROLE_PERMISSION": "ns_instances:id:terminate:",
459 "METHODS": ("POST",),
460 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
463 "METHODS": ("POST",),
464 "ROLE_PERMISSION": "ns_instances:id:migrate:",
467 "METHODS": ("POST",),
468 "ROLE_PERMISSION": "ns_instances:id:action:",
471 "METHODS": ("POST",),
472 "ROLE_PERMISSION": "ns_instances:id:update:",
475 "METHODS": ("POST",),
476 "ROLE_PERMISSION": "ns_instances:id:verticalscale:",
482 "ROLE_PERMISSION": "ns_instances:opps:",
485 "ROLE_PERMISSION": "ns_instances:opps:id:",
490 "ROLE_PERMISSION": "vnf_instances:",
491 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
495 "ROLE_PERMISSION": "vnf_instances:",
496 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
499 "METHODS": ("GET", "POST"),
500 "ROLE_PERMISSION": "ns_subscriptions:",
502 "METHODS": ("GET", "DELETE"),
503 "ROLE_PERMISSION": "ns_subscriptions:id:",
511 "METHODS": ("GET", "POST"),
512 "ROLE_PERMISSION": "vnflcm_instances:",
514 "METHODS": ("GET", "DELETE"),
515 "ROLE_PERMISSION": "vnflcm_instances:id:",
517 "METHODS": ("POST",),
518 "ROLE_PERMISSION": "vnflcm_instances:id:scale:",
521 "METHODS": ("POST",),
522 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:",
525 "METHODS": ("POST",),
526 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:",
532 "ROLE_PERMISSION": "vnf_instances:opps:",
535 "ROLE_PERMISSION": "vnf_instances:opps:id:",
539 "METHODS": ("GET", "POST"),
540 "ROLE_PERMISSION": "vnflcm_subscriptions:",
542 "METHODS": ("GET", "DELETE"),
543 "ROLE_PERMISSION": "vnflcm_subscriptions:id:",
550 "netslice_templates_content": {
551 "METHODS": ("GET", "POST"),
552 "ROLE_PERMISSION": "slice_templates:",
554 "METHODS": ("GET", "PUT", "DELETE"),
555 "ROLE_PERMISSION": "slice_templates:id:",
558 "netslice_templates": {
559 "METHODS": ("GET", "POST"),
560 "ROLE_PERMISSION": "slice_templates:",
562 "METHODS": ("GET", "DELETE"),
564 "ROLE_PERMISSION": "slice_templates:id:",
566 "METHODS": ("GET", "PUT"),
567 "ROLE_PERMISSION": "slice_templates:id:content:",
570 "METHODS": ("GET",), # descriptor inside package
571 "ROLE_PERMISSION": "slice_templates:id:content:",
575 "ROLE_PERMISSION": "slice_templates:id:content:",
581 "TODO": ("GET", "POST"),
582 "<ID>": {"TODO": ("GET", "DELETE")},
588 "netslice_instances_content": {
589 "METHODS": ("GET", "POST"),
590 "ROLE_PERMISSION": "slice_instances:",
592 "METHODS": ("GET", "DELETE"),
593 "ROLE_PERMISSION": "slice_instances:id:",
596 "netslice_instances": {
597 "METHODS": ("GET", "POST"),
598 "ROLE_PERMISSION": "slice_instances:",
600 "METHODS": ("GET", "DELETE"),
601 "ROLE_PERMISSION": "slice_instances:id:",
603 "METHODS": ("POST",),
604 "ROLE_PERMISSION": "slice_instances:id:terminate:",
607 "METHODS": ("POST",),
608 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
611 "METHODS": ("POST",),
612 "ROLE_PERMISSION": "slice_instances:id:action:",
618 "ROLE_PERMISSION": "slice_instances:opps:",
621 "ROLE_PERMISSION": "slice_instances:opps:id:",
633 "ROLE_PERMISSION": "reports:id:",
643 "METHODS": ("GET", "PATCH"),
644 "ROLE_PERMISSION": "alarms:",
646 "METHODS": ("GET", "PATCH"),
647 "ROLE_PERMISSION": "alarms:id:",
655 class NbiException(Exception):
656 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
657 Exception.__init
__(self
, message
)
658 self
.http_code
= http_code
661 class Server(object):
663 # to decode bytes to str
664 reader
= getreader("utf-8")
668 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
669 self
.engine
= Engine(self
.authenticator
)
671 def _format_in(self
, kwargs
):
674 if cherrypy
.request
.body
.length
:
675 error_text
= "Invalid input format "
677 if "Content-Type" in cherrypy
.request
.headers
:
678 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
679 error_text
= "Invalid json format "
680 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
681 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
682 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
683 error_text
= "Invalid yaml format "
685 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
687 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
689 "application/binary" in cherrypy
.request
.headers
["Content-Type"]
690 or "application/gzip"
691 in cherrypy
.request
.headers
["Content-Type"]
692 or "application/zip" in cherrypy
.request
.headers
["Content-Type"]
693 or "text/plain" in cherrypy
.request
.headers
["Content-Type"]
695 indata
= cherrypy
.request
.body
# .read()
697 "multipart/form-data"
698 in cherrypy
.request
.headers
["Content-Type"]
700 if "descriptor_file" in kwargs
:
701 filecontent
= kwargs
.pop("descriptor_file")
702 if not filecontent
.file:
704 "empty file or content", HTTPStatus
.BAD_REQUEST
706 indata
= filecontent
.file # .read()
707 if filecontent
.content_type
.value
:
708 cherrypy
.request
.headers
[
710 ] = filecontent
.content_type
.value
712 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
713 # "Only 'Content-Type' of type 'application/json' or
714 # 'application/yaml' for input format are available")
715 error_text
= "Invalid yaml format "
717 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
719 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
721 error_text
= "Invalid yaml format "
722 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
723 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
728 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
731 for k
, v
in kwargs
.items():
732 if isinstance(v
, str):
737 kwargs
[k
] = yaml
.load(v
, Loader
=yaml
.SafeLoader
)
743 or k
.endswith(".gte")
744 or k
.endswith(".lte")
753 elif v
.find(",") > 0:
754 kwargs
[k
] = v
.split(",")
755 elif isinstance(v
, (list, tuple)):
756 for index
in range(0, len(v
)):
761 v
[index
] = yaml
.load(v
[index
], Loader
=yaml
.SafeLoader
)
766 except (ValueError, yaml
.YAMLError
) as exc
:
767 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
768 except KeyError as exc
:
770 "Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
772 except Exception as exc
:
773 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
776 def _format_out(data
, token_info
=None, _format
=None):
778 return string of dictionary data according to requested json, yaml, xml. By default json
779 :param data: response to be sent. Can be a dict, text or file
780 :param token_info: Contains among other username and project
781 :param _format: The format to be set as Content-Type if data is a file
784 accept
= cherrypy
.request
.headers
.get("Accept")
786 if accept
and "text/html" in accept
:
788 data
, cherrypy
.request
, cherrypy
.response
, token_info
790 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
792 elif hasattr(data
, "read"): # file object
794 cherrypy
.response
.headers
["Content-Type"] = _format
795 elif "b" in data
.mode
: # binariy asssumig zip
796 cherrypy
.response
.headers
["Content-Type"] = "application/zip"
798 cherrypy
.response
.headers
["Content-Type"] = "text/plain"
799 # TODO check that cherrypy close file. If not implement pending things to close per thread next
802 if "text/html" in accept
:
804 data
, cherrypy
.request
, cherrypy
.response
, token_info
806 elif "application/yaml" in accept
or "*/*" in accept
:
808 elif "application/json" in accept
or (
809 cherrypy
.response
.status
and cherrypy
.response
.status
>= 300
811 cherrypy
.response
.headers
[
813 ] = "application/json; charset=utf-8"
814 a
= json
.dumps(data
, indent
=4) + "\n"
815 return a
.encode("utf8")
816 cherrypy
.response
.headers
["Content-Type"] = "application/yaml"
817 return yaml
.safe_dump(
821 default_flow_style
=False,
825 ) # , canonical=True, default_style='"'
828 def index(self
, *args
, **kwargs
):
831 if cherrypy
.request
.method
== "GET":
832 token_info
= self
.authenticator
.authorize()
833 outdata
= token_info
# Home page
835 raise cherrypy
.HTTPError(
836 HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
837 "Method {} not allowed for tokens".format(cherrypy
.request
.method
),
840 return self
._format
_out
(outdata
, token_info
)
842 except (EngineException
, AuthException
) as e
:
843 # cherrypy.log("index Exception {}".format(e))
844 cherrypy
.response
.status
= e
.http_code
.value
845 return self
._format
_out
("Welcome to OSM!", token_info
)
848 def version(self
, *args
, **kwargs
):
849 # TODO consider to remove and provide version using the static version file
851 if cherrypy
.request
.method
!= "GET":
853 "Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
857 "Invalid URL or query string for version",
858 HTTPStatus
.METHOD_NOT_ALLOWED
,
860 # TODO include version of other modules, pick up from some kafka admin message
861 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
862 return self
._format
_out
(osm_nbi_version
)
863 except NbiException
as e
:
864 cherrypy
.response
.status
= e
.http_code
.value
866 "code": e
.http_code
.name
,
867 "status": e
.http_code
.value
,
870 return self
._format
_out
(problem_details
, None)
875 "user_domain_name": cherrypy
.tree
.apps
["/osm"]
876 .config
["authentication"]
877 .get("user_domain_name"),
878 "project_domain_name": cherrypy
.tree
.apps
["/osm"]
879 .config
["authentication"]
880 .get("project_domain_name"),
882 return self
._format
_out
(domains
)
883 except NbiException
as e
:
884 cherrypy
.response
.status
= e
.http_code
.value
886 "code": e
.http_code
.name
,
887 "status": e
.http_code
.value
,
890 return self
._format
_out
(problem_details
, None)
893 def _format_login(token_info
):
895 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
897 :param token_info: Dictionary with token content
900 cherrypy
.request
.login
= token_info
.get("username", "-")
901 if token_info
.get("project_name"):
902 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
903 if token_info
.get("id"):
904 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
906 # NS Fault Management
918 if topic
== "alarms":
920 method
= cherrypy
.request
.method
921 role_permission
= self
._check
_valid
_url
_method
(
922 method
, "nsfm", version
, topic
, None, None, *args
924 query_string_operations
= self
._extract
_query
_string
_operations
(
928 self
.authenticator
.authorize(
929 role_permission
, query_string_operations
, None
932 # to handle get request
933 if cherrypy
.request
.method
== "GET":
934 # if request is on basis of uuid
935 if uuid
and uuid
!= "None":
937 alarm
= self
.engine
.db
.get_one("alarms", {"uuid": uuid
})
938 alarm_action
= self
.engine
.db
.get_one(
939 "alarms_action", {"uuid": uuid
}
941 alarm
.update(alarm_action
)
942 vnf
= self
.engine
.db
.get_one(
943 "vnfrs", {"nsr-id-ref": alarm
["tags"]["ns_id"]}
945 alarm
["vnf-id"] = vnf
["_id"]
946 return self
._format
_out
(str(alarm
))
948 return self
._format
_out
("Please provide valid alarm uuid")
949 elif ns_id
and ns_id
!= "None":
950 # if request is on basis of ns_id
952 alarms
= self
.engine
.db
.get_list(
953 "alarms", {"tags.ns_id": ns_id
}
956 alarm_action
= self
.engine
.db
.get_one(
957 "alarms_action", {"uuid": alarm
["uuid"]}
959 alarm
.update(alarm_action
)
960 return self
._format
_out
(str(alarms
))
962 return self
._format
_out
("Please provide valid ns id")
964 # to return only alarm which are related to given project
965 project
= self
.engine
.db
.get_one(
966 "projects", {"name": project_name
}
968 project_id
= project
.get("_id")
969 ns_list
= self
.engine
.db
.get_list(
970 "nsrs", {"_admin.projects_read": project_id
}
974 ns_ids
.append(ns
.get("_id"))
975 alarms
= self
.engine
.db
.get_list("alarms")
979 if alarm
["tags"]["ns_id"] in ns_ids
981 for alrm
in alarm_list
:
982 action
= self
.engine
.db
.get_one(
983 "alarms_action", {"uuid": alrm
.get("uuid")}
986 return self
._format
_out
(str(alarm_list
))
987 # to handle patch request for alarm update
988 elif cherrypy
.request
.method
== "PATCH":
989 data
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
991 # check if uuid is valid
992 self
.engine
.db
.get_one("alarms", {"uuid": data
.get("uuid")})
994 return self
._format
_out
("Please provide valid alarm uuid.")
995 if data
.get("is_enable") is not None:
996 if data
.get("is_enable"):
999 alarm_status
= "disabled"
1000 self
.engine
.db
.set_one(
1002 {"uuid": data
.get("uuid")},
1003 {"alarm_status": alarm_status
},
1006 self
.engine
.db
.set_one(
1008 {"uuid": data
.get("uuid")},
1009 {"threshold": data
.get("threshold")},
1011 return self
._format
_out
("Alarm updated")
1012 except Exception as e
:
1013 cherrypy
.response
.status
= e
.http_code
.value
1027 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1028 http_code_name
= e
.http_code
.name
1029 cherrypy
.log("Exception {}".format(e
))
1032 cherrypy
.response
.status
1033 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1034 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1035 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1037 "code": http_code_name
,
1038 "status": http_code_value
,
1041 return self
._format
_out
(problem_details
)
1044 def token(self
, method
, token_id
=None, kwargs
=None):
1046 # self.engine.load_dbase(cherrypy.request.app.config)
1047 indata
= self
._format
_in
(kwargs
)
1048 if not isinstance(indata
, dict):
1050 "Expected application/yaml or application/json Content-Type",
1051 HTTPStatus
.BAD_REQUEST
,
1055 token_info
= self
.authenticator
.authorize()
1057 self
._format
_login
(token_info
)
1059 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
1061 outdata
= self
.authenticator
.get_token_list(token_info
)
1062 elif method
== "POST":
1064 token_info
= self
.authenticator
.authorize()
1068 indata
.update(kwargs
)
1069 # This is needed to log the user when authentication fails
1070 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
1071 outdata
= token_info
= self
.authenticator
.new_token(
1072 token_info
, indata
, cherrypy
.request
.remote
1074 cherrypy
.session
["Authorization"] = outdata
["_id"]
1075 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
1077 self
._format
_login
(token_info
)
1078 # password expiry check
1079 if self
.authenticator
.check_password_expiry(outdata
):
1081 "id": outdata
["id"],
1082 "message": "change_password",
1083 "user_id": outdata
["user_id"],
1085 # cherrypy.response.cookie["Authorization"] = outdata["id"]
1086 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
1087 elif method
== "DELETE":
1088 if not token_id
and "id" in kwargs
:
1089 token_id
= kwargs
["id"]
1091 token_info
= self
.authenticator
.authorize()
1093 self
._format
_login
(token_info
)
1094 token_id
= token_info
["_id"]
1095 outdata
= self
.authenticator
.del_token(token_id
)
1097 cherrypy
.session
["Authorization"] = "logout"
1098 # cherrypy.response.cookie["Authorization"] = token_id
1099 # cherrypy.response.cookie["Authorization"]['expires'] = 0
1102 "Method {} not allowed for token".format(method
),
1103 HTTPStatus
.METHOD_NOT_ALLOWED
,
1105 return self
._format
_out
(outdata
, token_info
)
1108 def test(self
, *args
, **kwargs
):
1109 if not cherrypy
.config
.get("server.enable_test") or (
1110 isinstance(cherrypy
.config
["server.enable_test"], str)
1111 and cherrypy
.config
["server.enable_test"].lower() == "false"
1113 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
1114 return "test URL is disabled"
1116 if args
and args
[0] == "help":
1118 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1119 "sleep/<time>\nmessage/topic\n</pre></html>"
1122 elif args
and args
[0] == "init":
1124 # self.engine.load_dbase(cherrypy.request.app.config)
1125 self
.engine
.create_admin()
1126 return "Done. User 'admin', password 'admin' created"
1128 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1129 return self
._format
_out
("Database already initialized")
1130 elif args
and args
[0] == "file":
1131 return cherrypy
.lib
.static
.serve_file(
1132 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1],
1136 elif args
and args
[0] == "file2":
1138 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1]
1140 f
= open(f_path
, "r")
1141 cherrypy
.response
.headers
["Content-type"] = "text/plain"
1144 elif len(args
) == 2 and args
[0] == "db-clear":
1145 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
1146 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
1147 elif len(args
) and args
[0] == "fs-clear":
1149 folders
= (args
[1],)
1151 folders
= self
.engine
.fs
.dir_ls(".")
1152 for folder
in folders
:
1153 self
.engine
.fs
.file_delete(folder
)
1154 return ",".join(folders
) + " folders deleted\n"
1155 elif args
and args
[0] == "login":
1156 if not cherrypy
.request
.headers
.get("Authorization"):
1157 cherrypy
.response
.headers
[
1159 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
1160 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1161 elif args
and args
[0] == "login2":
1162 if not cherrypy
.request
.headers
.get("Authorization"):
1163 cherrypy
.response
.headers
[
1165 ] = 'Bearer realm="Access to OSM site"'
1166 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1167 elif args
and args
[0] == "sleep":
1170 sleep_time
= int(args
[1])
1172 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1173 return self
._format
_out
("Database already initialized")
1174 thread_info
= cherrypy
.thread_data
1176 time
.sleep(sleep_time
)
1178 elif len(args
) >= 2 and args
[0] == "message":
1179 main_topic
= args
[1]
1180 return_text
= "<html><pre>{} ->\n".format(main_topic
)
1182 if cherrypy
.request
.method
== "POST":
1183 to_send
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
1184 for k
, v
in to_send
.items():
1185 self
.engine
.msg
.write(main_topic
, k
, v
)
1186 return_text
+= " {}: {}\n".format(k
, v
)
1187 elif cherrypy
.request
.method
== "GET":
1188 for k
, v
in kwargs
.items():
1189 v_dict
= yaml
.load(v
, Loader
=yaml
.SafeLoader
)
1190 self
.engine
.msg
.write(main_topic
, k
, v_dict
)
1191 return_text
+= " {}: {}\n".format(k
, v_dict
)
1192 except Exception as e
:
1193 return_text
+= "Error: " + str(e
)
1194 return_text
+= "</pre></html>\n"
1198 "<html><pre>\nheaders:\n args: {}\n".format(args
)
1199 + " kwargs: {}\n".format(kwargs
)
1200 + " headers: {}\n".format(cherrypy
.request
.headers
)
1201 + " path_info: {}\n".format(cherrypy
.request
.path_info
)
1202 + " query_string: {}\n".format(cherrypy
.request
.query_string
)
1203 + " session: {}\n".format(cherrypy
.session
)
1204 + " cookie: {}\n".format(cherrypy
.request
.cookie
)
1205 + " method: {}\n".format(cherrypy
.request
.method
)
1206 + " session: {}\n".format(cherrypy
.session
.get("fieldname"))
1209 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
1210 if cherrypy
.request
.body
.length
:
1211 return_text
+= " content: {}\n".format(
1213 cherrypy
.request
.body
.read(
1214 int(cherrypy
.request
.headers
.get("Content-Length", 0))
1219 return_text
+= "thread: {}\n".format(thread_info
)
1220 return_text
+= "</pre></html>"
1224 def _check_valid_url_method(method
, *args
):
1227 "URL must contain at least 'main_topic/version/topic'",
1228 HTTPStatus
.METHOD_NOT_ALLOWED
,
1231 reference
= valid_url_methods
1235 if not isinstance(reference
, dict):
1237 "URL contains unexpected extra items '{}'".format(arg
),
1238 HTTPStatus
.METHOD_NOT_ALLOWED
,
1241 if arg
in reference
:
1242 reference
= reference
[arg
]
1243 elif "<ID>" in reference
:
1244 reference
= reference
["<ID>"]
1245 elif "*" in reference
:
1246 # if there is content
1248 reference
= reference
["*"]
1252 "Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
1254 if "TODO" in reference
and method
in reference
["TODO"]:
1256 "Method {} not supported yet for this URL".format(method
),
1257 HTTPStatus
.NOT_IMPLEMENTED
,
1259 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
1261 "Method {} not supported for this URL".format(method
),
1262 HTTPStatus
.METHOD_NOT_ALLOWED
,
1264 return reference
["ROLE_PERMISSION"] + method
.lower()
1267 def _set_location_header(main_topic
, version
, topic
, id):
1269 Insert response header Location with the URL of created item base on URL params
1276 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1277 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(
1278 main_topic
, version
, topic
, id
1283 def _extract_query_string_operations(kwargs
, method
):
1289 query_string_operations
= []
1291 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1292 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
1293 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
1294 return query_string_operations
1297 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
1299 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1300 Check that users has rights to use them and returs the admin_query
1301 :param token_info: token_info rights obtained by token
1302 :param kwargs: query string input.
1303 :param method: http method: GET, POSST, PUT, ...
1305 :return: admin_query dictionary with keys:
1306 public: True, False or None
1307 force: True or False
1308 project_id: tuple with projects used for accessing an element
1309 set_project: tuple with projects that a created element will belong to
1310 method: show, list, delete, write
1314 "project_id": (token_info
["project_id"],),
1315 "username": token_info
["username"],
1316 "admin": token_info
["admin"],
1318 "allow_show_user_project_role": token_info
["allow_show_user_project_role"],
1322 if "FORCE" in kwargs
:
1324 kwargs
["FORCE"].lower() != "false"
1325 ): # if None or True set force to True
1326 admin_query
["force"] = True
1329 if "PUBLIC" in kwargs
:
1331 kwargs
["PUBLIC"].lower() != "false"
1332 ): # if None or True set public to True
1333 admin_query
["public"] = True
1335 admin_query
["public"] = False
1336 del kwargs
["PUBLIC"]
1338 if "ADMIN" in kwargs
:
1339 behave_as
= kwargs
.pop("ADMIN")
1340 if behave_as
.lower() != "false":
1341 if not token_info
["admin"]:
1343 "Only admin projects can use 'ADMIN' query string",
1344 HTTPStatus
.UNAUTHORIZED
,
1347 not behave_as
or behave_as
.lower() == "true"
1348 ): # convert True, None to empty list
1349 admin_query
["project_id"] = ()
1350 elif isinstance(behave_as
, (list, tuple)):
1351 admin_query
["project_id"] = behave_as
1352 else: # isinstance(behave_as, str)
1353 admin_query
["project_id"] = (behave_as
,)
1354 if "SET_PROJECT" in kwargs
:
1355 set_project
= kwargs
.pop("SET_PROJECT")
1357 admin_query
["set_project"] = list(admin_query
["project_id"])
1359 if isinstance(set_project
, str):
1360 set_project
= (set_project
,)
1361 if admin_query
["project_id"]:
1362 for p
in set_project
:
1363 if p
not in admin_query
["project_id"]:
1365 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1366 "'ADMIN='{p}'".format(p
=p
),
1367 HTTPStatus
.UNAUTHORIZED
,
1369 admin_query
["set_project"] = set_project
1372 # if "PROJECT_READ" in kwargs:
1373 # admin_query["project"] = kwargs.pop("project")
1374 # if admin_query["project"] == token_info["project_id"]:
1377 admin_query
["method"] = "show"
1379 admin_query
["method"] = "list"
1380 elif method
== "DELETE":
1381 admin_query
["method"] = "delete"
1383 admin_query
["method"] = "write"
1403 engine_session
= None
1405 if not main_topic
or not version
or not topic
:
1407 "URL must contain at least 'main_topic/version/topic'",
1408 HTTPStatus
.METHOD_NOT_ALLOWED
,
1410 if main_topic
not in (
1422 "URL main_topic '{}' not supported".format(main_topic
),
1423 HTTPStatus
.METHOD_NOT_ALLOWED
,
1427 "URL version '{}' not supported".format(version
),
1428 HTTPStatus
.METHOD_NOT_ALLOWED
,
1433 and "METHOD" in kwargs
1434 and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1436 method
= kwargs
.pop("METHOD")
1438 method
= cherrypy
.request
.method
1440 role_permission
= self
._check
_valid
_url
_method
(
1441 method
, main_topic
, version
, topic
, _id
, item
, *args
1443 query_string_operations
= self
._extract
_query
_string
_operations
(
1446 if main_topic
== "admin" and topic
== "tokens":
1447 return self
.token(method
, _id
, kwargs
)
1448 token_info
= self
.authenticator
.authorize(
1449 role_permission
, query_string_operations
, _id
1451 if main_topic
== "admin" and topic
== "domains":
1452 return self
.domain()
1453 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
1454 indata
= self
._format
_in
(kwargs
)
1455 engine_topic
= topic
1457 if item
and topic
!= "pm_jobs":
1460 if main_topic
== "nsd":
1461 engine_topic
= "nsds"
1462 elif main_topic
== "vnfpkgm":
1463 engine_topic
= "vnfds"
1464 if topic
== "vnfpkg_op_occs":
1465 engine_topic
= "vnfpkgops"
1466 if topic
== "vnf_packages" and item
== "action":
1467 engine_topic
= "vnfpkgops"
1468 elif main_topic
== "nslcm":
1469 engine_topic
= "nsrs"
1470 if topic
== "ns_lcm_op_occs":
1471 engine_topic
= "nslcmops"
1472 if topic
== "vnfrs" or topic
== "vnf_instances":
1473 engine_topic
= "vnfrs"
1474 elif main_topic
== "vnflcm":
1475 if topic
== "vnf_lcm_op_occs":
1476 engine_topic
= "vnflcmops"
1477 elif main_topic
== "nst":
1478 engine_topic
= "nsts"
1479 elif main_topic
== "nsilcm":
1480 engine_topic
= "nsis"
1481 if topic
== "nsi_lcm_op_occs":
1482 engine_topic
= "nsilcmops"
1483 elif main_topic
== "pdu":
1484 engine_topic
= "pdus"
1486 engine_topic
== "vims"
1487 ): # TODO this is for backward compatibility, it will be removed in the future
1488 engine_topic
= "vim_accounts"
1490 if topic
== "subscriptions":
1491 engine_topic
= main_topic
+ "_" + topic
1503 if item
in ("vnfd", "nsd", "nst"):
1504 path
= "$DESCRIPTOR"
1507 elif item
== "artifacts":
1511 file, _format
= self
.engine
.get_file(
1516 cherrypy
.request
.headers
.get("Accept"),
1520 outdata
= self
.engine
.get_item_list(
1521 engine_session
, engine_topic
, kwargs
, api_req
=True
1524 if item
== "reports":
1525 # TODO check that project_id (_id in this context) has permissions
1528 if "vcaStatusRefresh" in kwargs
:
1529 filter_q
= {"vcaStatusRefresh": kwargs
["vcaStatusRefresh"]}
1530 outdata
= self
.engine
.get_item(
1531 engine_session
, engine_topic
, _id
, filter_q
, True
1534 elif method
== "POST":
1535 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1537 "ns_descriptors_content",
1538 "vnf_packages_content",
1539 "netslice_templates_content",
1541 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1543 _id
, _
= self
.engine
.new_item(
1549 cherrypy
.request
.headers
,
1551 completed
= self
.engine
.upload_content(
1557 cherrypy
.request
.headers
,
1560 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1562 cherrypy
.response
.headers
["Transaction-Id"] = _id
1563 outdata
= {"id": _id
}
1564 elif topic
== "ns_instances_content":
1566 _id
, _
= self
.engine
.new_item(
1567 rollback
, engine_session
, engine_topic
, indata
, kwargs
1570 indata
["lcmOperationType"] = "instantiate"
1571 indata
["nsInstanceId"] = _id
1572 nslcmop_id
, _
= self
.engine
.new_item(
1573 rollback
, engine_session
, "nslcmops", indata
, None
1575 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1576 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1577 elif topic
== "ns_instances" and item
:
1578 indata
["lcmOperationType"] = item
1579 indata
["nsInstanceId"] = _id
1580 _id
, _
= self
.engine
.new_item(
1581 rollback
, engine_session
, "nslcmops", indata
, kwargs
1583 self
._set
_location
_header
(
1584 main_topic
, version
, "ns_lcm_op_occs", _id
1586 outdata
= {"id": _id
}
1587 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1588 elif topic
== "netslice_instances_content":
1589 # creates NetSlice_Instance_record (NSIR)
1590 _id
, _
= self
.engine
.new_item(
1591 rollback
, engine_session
, engine_topic
, indata
, kwargs
1593 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1594 indata
["lcmOperationType"] = "instantiate"
1595 indata
["netsliceInstanceId"] = _id
1596 nsilcmop_id
, _
= self
.engine
.new_item(
1597 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1599 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1600 elif topic
== "netslice_instances" and item
:
1601 indata
["lcmOperationType"] = item
1602 indata
["netsliceInstanceId"] = _id
1603 _id
, _
= self
.engine
.new_item(
1604 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1606 self
._set
_location
_header
(
1607 main_topic
, version
, "nsi_lcm_op_occs", _id
1609 outdata
= {"id": _id
}
1610 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1611 elif topic
== "vnf_packages" and item
== "action":
1612 indata
["lcmOperationType"] = item
1613 indata
["vnfPkgId"] = _id
1614 _id
, _
= self
.engine
.new_item(
1615 rollback
, engine_session
, "vnfpkgops", indata
, kwargs
1617 self
._set
_location
_header
(
1618 main_topic
, version
, "vnfpkg_op_occs", _id
1620 outdata
= {"id": _id
}
1621 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1622 elif topic
== "subscriptions":
1623 _id
, _
= self
.engine
.new_item(
1624 rollback
, engine_session
, engine_topic
, indata
, kwargs
1626 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1628 link
["self"] = cherrypy
.response
.headers
["Location"]
1631 "filter": indata
["filter"],
1632 "callbackUri": indata
["CallbackUri"],
1635 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1636 elif topic
== "vnf_instances" and item
:
1637 indata
["lcmOperationType"] = item
1638 indata
["vnfInstanceId"] = _id
1639 _id
, _
= self
.engine
.new_item(
1640 rollback
, engine_session
, "vnflcmops", indata
, kwargs
1642 self
._set
_location
_header
(
1643 main_topic
, version
, "vnf_lcm_op_occs", _id
1645 outdata
= {"id": _id
}
1646 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1648 _id
, op_id
= self
.engine
.new_item(
1654 cherrypy
.request
.headers
,
1656 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1657 outdata
= {"id": _id
}
1659 outdata
["op_id"] = op_id
1660 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1661 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1663 elif method
== "DELETE":
1665 outdata
= self
.engine
.del_item_list(
1666 engine_session
, engine_topic
, kwargs
1668 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1669 else: # len(args) > 1
1670 # for NS NSI generate an operation
1672 if topic
== "ns_instances_content" and not engine_session
["force"]:
1674 "lcmOperationType": "terminate",
1675 "nsInstanceId": _id
,
1678 op_id
, _
= self
.engine
.new_item(
1679 rollback
, engine_session
, "nslcmops", nslcmop_desc
, kwargs
1682 outdata
= {"_id": op_id
}
1684 topic
== "netslice_instances_content"
1685 and not engine_session
["force"]
1688 "lcmOperationType": "terminate",
1689 "netsliceInstanceId": _id
,
1692 op_id
, _
= self
.engine
.new_item(
1693 rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None
1696 outdata
= {"_id": op_id
}
1697 # if there is not any deletion in process, delete
1699 op_id
= self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1701 outdata
= {"op_id": op_id
}
1702 cherrypy
.response
.status
= (
1703 HTTPStatus
.ACCEPTED
.value
1705 else HTTPStatus
.NO_CONTENT
.value
1708 elif method
in ("PUT", "PATCH"):
1710 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1712 "Nothing to update. Provide payload and/or query string",
1713 HTTPStatus
.BAD_REQUEST
,
1716 item
in ("nsd_content", "package_content", "nst_content")
1719 completed
= self
.engine
.upload_content(
1725 cherrypy
.request
.headers
,
1728 cherrypy
.response
.headers
["Transaction-Id"] = id
1730 op_id
= self
.engine
.edit_item(
1731 engine_session
, engine_topic
, _id
, indata
, kwargs
1735 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1736 outdata
= {"op_id": op_id
}
1738 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1742 "Method {} not allowed".format(method
),
1743 HTTPStatus
.METHOD_NOT_ALLOWED
,
1746 # if Role information changes, it is needed to reload the information of roles
1747 if topic
== "roles" and method
!= "GET":
1748 self
.authenticator
.load_operation_to_allowed_roles()
1752 and method
== "DELETE"
1753 or topic
in ["users", "roles"]
1754 and method
in ["PUT", "PATCH", "DELETE"]
1756 self
.authenticator
.remove_token_from_cache()
1758 return self
._format
_out
(outdata
, token_info
, _format
)
1759 except Exception as e
:
1773 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1774 http_code_name
= e
.http_code
.name
1775 cherrypy
.log("Exception {}".format(e
))
1778 cherrypy
.response
.status
1779 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1780 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1781 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1782 if hasattr(outdata
, "close"): # is an open file
1786 for rollback_item
in rollback
:
1788 if rollback_item
.get("operation") == "set":
1789 self
.engine
.db
.set_one(
1790 rollback_item
["topic"],
1791 {"_id": rollback_item
["_id"]},
1792 rollback_item
["content"],
1793 fail_on_empty
=False,
1795 elif rollback_item
.get("operation") == "del_list":
1796 self
.engine
.db
.del_list(
1797 rollback_item
["topic"],
1798 rollback_item
["filter"],
1799 fail_on_empty
=False,
1802 self
.engine
.db
.del_one(
1803 rollback_item
["topic"],
1804 {"_id": rollback_item
["_id"]},
1805 fail_on_empty
=False,
1807 except Exception as e2
:
1808 rollback_error_text
= "Rollback Exception {}: {}".format(
1811 cherrypy
.log(rollback_error_text
)
1812 error_text
+= ". " + rollback_error_text
1813 # if isinstance(e, MsgException):
1814 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1815 # engine_topic[:-1], method, error_text)
1817 "code": http_code_name
,
1818 "status": http_code_value
,
1819 "detail": error_text
,
1821 return self
._format
_out
(problem_details
, token_info
)
1822 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1825 self
._format
_login
(token_info
)
1826 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1827 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1828 if outdata
.get(logging_id
):
1829 cherrypy
.request
.login
+= ";{}={}".format(
1830 logging_id
, outdata
[logging_id
][:36]
1834 def _start_service():
1836 Callback function called when cherrypy.engine starts
1837 Override configuration with env variables
1838 Set database, storage, message configuration
1839 Init database with admin/admin user password
1842 global subscription_thread
1843 cherrypy
.log
.error("Starting osm_nbi")
1844 # update general cherrypy configuration
1847 engine_config
= cherrypy
.tree
.apps
["/osm"].config
1848 for k
, v
in environ
.items():
1849 if not k
.startswith("OSMNBI_"):
1851 k1
, _
, k2
= k
[7:].lower().partition("_")
1855 # update static configuration
1856 if k
== "OSMNBI_STATIC_DIR":
1857 engine_config
["/static"]["tools.staticdir.dir"] = v
1858 engine_config
["/static"]["tools.staticdir.on"] = True
1859 elif k
== "OSMNBI_SOCKET_PORT" or k
== "OSMNBI_SERVER_PORT":
1860 update_dict
["server.socket_port"] = int(v
)
1861 elif k
== "OSMNBI_SOCKET_HOST" or k
== "OSMNBI_SERVER_HOST":
1862 update_dict
["server.socket_host"] = v
1863 elif k1
in ("server", "test", "auth", "log"):
1864 update_dict
[k1
+ "." + k2
] = v
1865 elif k1
in ("message", "database", "storage", "authentication"):
1866 # k2 = k2.replace('_', '.')
1867 if k2
in ("port", "db_port"):
1868 engine_config
[k1
][k2
] = int(v
)
1870 engine_config
[k1
][k2
] = v
1872 except ValueError as e
:
1873 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1874 except Exception as e
:
1875 cherrypy
.log
.warn("skipping environ '{}' on exception '{}'".format(k
, e
))
1878 cherrypy
.config
.update(update_dict
)
1879 engine_config
["global"].update(update_dict
)
1882 log_format_simple
= (
1883 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1885 log_formatter_simple
= logging
.Formatter(
1886 log_format_simple
, datefmt
="%Y-%m-%dT%H:%M:%S"
1888 logger_server
= logging
.getLogger("cherrypy.error")
1889 logger_access
= logging
.getLogger("cherrypy.access")
1890 logger_cherry
= logging
.getLogger("cherrypy")
1891 logger_nbi
= logging
.getLogger("nbi")
1893 if "log.file" in engine_config
["global"]:
1894 file_handler
= logging
.handlers
.RotatingFileHandler(
1895 engine_config
["global"]["log.file"], maxBytes
=100e6
, backupCount
=9, delay
=0
1897 file_handler
.setFormatter(log_formatter_simple
)
1898 logger_cherry
.addHandler(file_handler
)
1899 logger_nbi
.addHandler(file_handler
)
1900 # log always to standard output
1901 for format_
, logger
in {
1902 "nbi.server %(filename)s:%(lineno)s": logger_server
,
1903 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1904 "%(name)s %(filename)s:%(lineno)s": logger_nbi
,
1906 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1907 log_formatter_cherry
= logging
.Formatter(
1908 log_format_cherry
, datefmt
="%Y-%m-%dT%H:%M:%S"
1910 str_handler
= logging
.StreamHandler()
1911 str_handler
.setFormatter(log_formatter_cherry
)
1912 logger
.addHandler(str_handler
)
1914 if engine_config
["global"].get("log.level"):
1915 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1916 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1918 # logging other modules
1919 for k1
, logname
in {
1920 "message": "nbi.msg",
1921 "database": "nbi.db",
1922 "storage": "nbi.fs",
1924 engine_config
[k1
]["logger_name"] = logname
1925 logger_module
= logging
.getLogger(logname
)
1926 if "logfile" in engine_config
[k1
]:
1927 file_handler
= logging
.handlers
.RotatingFileHandler(
1928 engine_config
[k1
]["logfile"], maxBytes
=100e6
, backupCount
=9, delay
=0
1930 file_handler
.setFormatter(log_formatter_simple
)
1931 logger_module
.addHandler(file_handler
)
1932 if "loglevel" in engine_config
[k1
]:
1933 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1934 # TODO add more entries, e.g.: storage
1935 cherrypy
.tree
.apps
["/osm"].root
.engine
.start(engine_config
)
1936 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.start(engine_config
)
1937 cherrypy
.tree
.apps
["/osm"].root
.engine
.init_db(target_version
=database_version
)
1938 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.init_db(
1939 target_version
=auth_database_version
1942 # start subscriptions thread:
1943 subscription_thread
= SubscriptionThread(
1944 config
=engine_config
, engine
=nbi_server
.engine
1946 subscription_thread
.start()
1947 # Do not capture except SubscriptionException
1949 backend
= engine_config
["authentication"]["backend"]
1951 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1952 nbi_version
, nbi_version_date
, backend
1957 def _stop_service():
1959 Callback function called when cherrypy.engine stops
1960 TODO: Ending database connections.
1962 global subscription_thread
1963 if subscription_thread
:
1964 subscription_thread
.terminate()
1965 subscription_thread
= None
1966 cherrypy
.tree
.apps
["/osm"].root
.engine
.stop()
1967 cherrypy
.log
.error("Stopping osm_nbi")
1970 def nbi(config_file
):
1974 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1975 # 'tools.sessions.on': True,
1976 # 'tools.response_headers.on': True,
1977 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1980 # cherrypy.Server.ssl_module = 'builtin'
1981 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1982 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1983 # cherrypy.Server.thread_pool = 10
1984 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1986 # cherrypy.config.update({'tools.auth_basic.on': True,
1987 # 'tools.auth_basic.realm': 'localhost',
1988 # 'tools.auth_basic.checkpassword': validate_password})
1989 nbi_server
= Server()
1990 cherrypy
.engine
.subscribe("start", _start_service
)
1991 cherrypy
.engine
.subscribe("stop", _stop_service
)
1992 cherrypy
.quickstart(nbi_server
, "/osm", config_file
)
1997 """Usage: {} [options]
1998 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1999 -h|--help: shows this help
2004 # --log-socket-host HOST: send logs to this host")
2005 # --log-socket-port PORT: send logs using this port (default: 9022)")
2008 if __name__
== "__main__":
2010 # load parameters and configuration
2011 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
2012 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
2015 if o
in ("-h", "--help"):
2018 elif o
in ("-c", "--config"):
2020 # elif o == "--log-socket-port":
2021 # log_socket_port = a
2022 # elif o == "--log-socket-host":
2023 # log_socket_host = a
2024 # elif o == "--log-file":
2027 assert False, "Unhandled option"
2029 if not path
.isfile(config_file
):
2031 "configuration file '{}' that not exist".format(config_file
),
2036 for config_file
in (
2037 __file__
[: __file__
.rfind(".")] + ".cfg",
2041 if path
.isfile(config_file
):
2045 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
2050 except getopt
.GetoptError
as e
:
2051 print(str(e
), file=sys
.stderr
)