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
92 /vnf_instances (also vnfrs for compatibility) O
108 /vim_accounts (also vims for compatibility) O O
122 /netslice_templates_content O O
124 /netslice_templates O O
128 /artifacts[/<artifactPath>] O
130 /<subscriptionId> X X
133 /netslice_instances_content O O
134 /<SliceInstanceId> O O
135 /netslice_instances O O
136 /<SliceInstanceId> O O
141 /<nsiLcmOpOccId> O O O
143 /<subscriptionId> X X
146 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
147 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
148 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
149 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
151 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
152 item of the array, that is, pass if any item of the array pass the filter.
153 It allows both ne and neq for not equal
154 TODO: 4.3.3 Attribute selectors
155 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
156 (none) … same as “exclude_default”
157 all_fields … all attributes.
158 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
159 conditionally mandatory, and that are not provided in <list>.
160 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
161 are not conditionally mandatory, and that are provided in <list>.
162 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
163 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
164 the particular resource
165 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
166 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
167 present specification for the particular resource, but that are not part of <list>
168 Additionally it admits some administrator values:
169 FORCE: To force operations skipping dependency checkings
170 ADMIN: To act as an administrator or a different project
171 PUBLIC: To get public descriptors or set a descriptor as public
172 SET_PROJECT: To make a descriptor available for other project
174 Header field name Reference Example Descriptions
175 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
176 This header field shall be present if the response is expected to have a non-empty message body.
177 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
178 This header field shall be present if the request has a non-empty message body.
179 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
180 Details are specified in clause 4.5.3.
181 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
182 Header field name Reference Example Descriptions
183 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
184 This header field shall be present if the response has a non-empty message body.
185 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
186 new resource has been created.
187 This header field shall be present if the response status code is 201 or 3xx.
188 In the present document this header field is also used if the response status code is 202 and a new resource was
190 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
191 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
193 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
195 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
196 response, and the total length of the file.
197 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
200 valid_query_string
= ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
201 # ^ Contains possible administrative query string words:
202 # ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
203 # (not owned by my session project).
204 # PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
205 # FORCE=True(by default)|False: Force edition/deletion operations
206 # SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
208 valid_url_methods
= {
209 # contains allowed URL and methods, and the role_permission name
213 "METHODS": ("GET", "POST", "DELETE"),
214 "ROLE_PERMISSION": "tokens:",
215 "<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"},
218 "METHODS": ("GET", "POST"),
219 "ROLE_PERMISSION": "users:",
221 "METHODS": ("GET", "DELETE", "PATCH"),
222 "ROLE_PERMISSION": "users:id:",
226 "METHODS": ("GET", "POST"),
227 "ROLE_PERMISSION": "projects:",
229 "METHODS": ("GET", "DELETE", "PATCH"),
230 "ROLE_PERMISSION": "projects:id:",
234 "METHODS": ("GET", "POST"),
235 "ROLE_PERMISSION": "roles:",
237 "METHODS": ("GET", "DELETE", "PATCH"),
238 "ROLE_PERMISSION": "roles:id:",
242 "METHODS": ("GET", "POST"),
243 "ROLE_PERMISSION": "vims:",
245 "METHODS": ("GET", "DELETE", "PATCH"),
246 "ROLE_PERMISSION": "vims:id:",
250 "METHODS": ("GET", "POST"),
251 "ROLE_PERMISSION": "vim_accounts:",
253 "METHODS": ("GET", "DELETE", "PATCH"),
254 "ROLE_PERMISSION": "vim_accounts:id:",
258 "METHODS": ("GET", "POST"),
259 "ROLE_PERMISSION": "wim_accounts:",
261 "METHODS": ("GET", "DELETE", "PATCH"),
262 "ROLE_PERMISSION": "wim_accounts:id:",
266 "METHODS": ("GET", "POST"),
267 "ROLE_PERMISSION": "sdn_controllers:",
269 "METHODS": ("GET", "DELETE", "PATCH"),
270 "ROLE_PERMISSION": "sdn_controllers:id:",
274 "METHODS": ("GET", "POST"),
275 "ROLE_PERMISSION": "k8sclusters:",
277 "METHODS": ("GET", "DELETE", "PATCH"),
278 "ROLE_PERMISSION": "k8sclusters:id:",
282 "METHODS": ("GET", "POST"),
283 "ROLE_PERMISSION": "vca:",
285 "METHODS": ("GET", "DELETE", "PATCH"),
286 "ROLE_PERMISSION": "vca:id:",
290 "METHODS": ("GET", "POST"),
291 "ROLE_PERMISSION": "k8srepos:",
293 "METHODS": ("GET", "DELETE"),
294 "ROLE_PERMISSION": "k8srepos:id:",
298 "METHODS": ("GET", "POST"),
299 "ROLE_PERMISSION": "osmrepos:",
301 "METHODS": ("GET", "DELETE", "PATCH"),
302 "ROLE_PERMISSION": "osmrepos:id:",
307 "ROLE_PERMISSION": "domains:",
314 "METHODS": ("GET", "POST"),
315 "ROLE_PERMISSION": "pduds:",
317 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
318 "ROLE_PERMISSION": "pduds:id:",
325 "ns_descriptors_content": {
326 "METHODS": ("GET", "POST"),
327 "ROLE_PERMISSION": "nsds:",
329 "METHODS": ("GET", "PUT", "DELETE"),
330 "ROLE_PERMISSION": "nsds:id:",
334 "METHODS": ("GET", "POST"),
335 "ROLE_PERMISSION": "nsds:",
337 "METHODS": ("GET", "DELETE", "PATCH"),
338 "ROLE_PERMISSION": "nsds:id:",
340 "METHODS": ("GET", "PUT"),
341 "ROLE_PERMISSION": "nsds:id:content:",
344 "METHODS": ("GET",), # descriptor inside package
345 "ROLE_PERMISSION": "nsds:id:content:",
349 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
355 "TODO": ("GET", "POST"),
357 "TODO": ("GET", "DELETE", "PATCH"),
358 "pnfd_content": {"TODO": ("GET", "PUT")},
362 "TODO": ("GET", "POST"),
363 "<ID>": {"TODO": ("GET", "DELETE")},
369 "vnf_packages_content": {
370 "METHODS": ("GET", "POST"),
371 "ROLE_PERMISSION": "vnfds:",
373 "METHODS": ("GET", "PUT", "DELETE"),
374 "ROLE_PERMISSION": "vnfds:id:",
378 "METHODS": ("GET", "POST"),
379 "ROLE_PERMISSION": "vnfds:",
381 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
382 "ROLE_PERMISSION": "vnfds:id:",
384 "METHODS": ("GET", "PUT"), # package
385 "ROLE_PERMISSION": "vnfds:id:",
389 "ROLE_PERMISSION": "vnfds:id:upload:",
393 "METHODS": ("GET",), # descriptor inside package
394 "ROLE_PERMISSION": "vnfds:id:content:",
398 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
402 "METHODS": ("POST",),
403 "ROLE_PERMISSION": "vnfds:id:action:",
408 "TODO": ("GET", "POST"),
409 "<ID>": {"TODO": ("GET", "DELETE")},
413 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
414 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"},
420 "ns_instances_content": {
421 "METHODS": ("GET", "POST"),
422 "ROLE_PERMISSION": "ns_instances:",
424 "METHODS": ("GET", "DELETE"),
425 "ROLE_PERMISSION": "ns_instances:id:",
429 "METHODS": ("GET", "POST"),
430 "ROLE_PERMISSION": "ns_instances:",
432 "METHODS": ("GET", "DELETE"),
433 "ROLE_PERMISSION": "ns_instances:id:",
435 "METHODS": ("POST",),
436 "ROLE_PERMISSION": "ns_instances:id:scale:",
439 "METHODS": ("POST",),
440 "ROLE_PERMISSION": "ns_instances:id:terminate:",
443 "METHODS": ("POST",),
444 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
447 "METHODS": ("POST",),
448 "ROLE_PERMISSION": "ns_instances:id:action:",
454 "ROLE_PERMISSION": "ns_instances:opps:",
457 "ROLE_PERMISSION": "ns_instances:opps:id:",
462 "ROLE_PERMISSION": "vnf_instances:",
463 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
467 "ROLE_PERMISSION": "vnf_instances:",
468 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
471 "METHODS": ("GET", "POST"),
472 "ROLE_PERMISSION": "ns_subscriptions:",
474 "METHODS": ("GET", "DELETE"),
475 "ROLE_PERMISSION": "ns_subscriptions:id:",
482 "netslice_templates_content": {
483 "METHODS": ("GET", "POST"),
484 "ROLE_PERMISSION": "slice_templates:",
486 "METHODS": ("GET", "PUT", "DELETE"),
487 "ROLE_PERMISSION": "slice_templates:id:",
490 "netslice_templates": {
491 "METHODS": ("GET", "POST"),
492 "ROLE_PERMISSION": "slice_templates:",
494 "METHODS": ("GET", "DELETE"),
496 "ROLE_PERMISSION": "slice_templates:id:",
498 "METHODS": ("GET", "PUT"),
499 "ROLE_PERMISSION": "slice_templates:id:content:",
502 "METHODS": ("GET",), # descriptor inside package
503 "ROLE_PERMISSION": "slice_templates:id:content:",
507 "ROLE_PERMISSION": "slice_templates:id:content:",
513 "TODO": ("GET", "POST"),
514 "<ID>": {"TODO": ("GET", "DELETE")},
520 "netslice_instances_content": {
521 "METHODS": ("GET", "POST"),
522 "ROLE_PERMISSION": "slice_instances:",
524 "METHODS": ("GET", "DELETE"),
525 "ROLE_PERMISSION": "slice_instances:id:",
528 "netslice_instances": {
529 "METHODS": ("GET", "POST"),
530 "ROLE_PERMISSION": "slice_instances:",
532 "METHODS": ("GET", "DELETE"),
533 "ROLE_PERMISSION": "slice_instances:id:",
535 "METHODS": ("POST",),
536 "ROLE_PERMISSION": "slice_instances:id:terminate:",
539 "METHODS": ("POST",),
540 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
543 "METHODS": ("POST",),
544 "ROLE_PERMISSION": "slice_instances:id:action:",
550 "ROLE_PERMISSION": "slice_instances:opps:",
553 "ROLE_PERMISSION": "slice_instances:opps:id:",
565 "ROLE_PERMISSION": "reports:id:",
574 "alarms": {"METHODS": ("GET", "PATCH"),
575 "ROLE_PERMISSION": "alarms:",
576 "<ID>": {"METHODS": ("GET", "PATCH"),
577 "ROLE_PERMISSION": "alarms:id:",
585 class NbiException(Exception):
586 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
587 Exception.__init
__(self
, message
)
588 self
.http_code
= http_code
591 class Server(object):
593 # to decode bytes to str
594 reader
= getreader("utf-8")
598 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
599 self
.engine
= Engine(self
.authenticator
)
601 def _format_in(self
, kwargs
):
604 if cherrypy
.request
.body
.length
:
605 error_text
= "Invalid input format "
607 if "Content-Type" in cherrypy
.request
.headers
:
608 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
609 error_text
= "Invalid json format "
610 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
611 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
612 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
613 error_text
= "Invalid yaml format "
615 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
617 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
619 "application/binary" in cherrypy
.request
.headers
["Content-Type"]
620 or "application/gzip"
621 in cherrypy
.request
.headers
["Content-Type"]
622 or "application/zip" in cherrypy
.request
.headers
["Content-Type"]
623 or "text/plain" in cherrypy
.request
.headers
["Content-Type"]
625 indata
= cherrypy
.request
.body
# .read()
627 "multipart/form-data"
628 in cherrypy
.request
.headers
["Content-Type"]
630 if "descriptor_file" in kwargs
:
631 filecontent
= kwargs
.pop("descriptor_file")
632 if not filecontent
.file:
634 "empty file or content", HTTPStatus
.BAD_REQUEST
636 indata
= filecontent
.file # .read()
637 if filecontent
.content_type
.value
:
638 cherrypy
.request
.headers
[
640 ] = filecontent
.content_type
.value
642 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
643 # "Only 'Content-Type' of type 'application/json' or
644 # 'application/yaml' for input format are available")
645 error_text
= "Invalid yaml format "
647 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
649 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
651 error_text
= "Invalid yaml format "
652 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
653 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
658 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
661 for k
, v
in kwargs
.items():
662 if isinstance(v
, str):
667 kwargs
[k
] = yaml
.load(v
, Loader
=yaml
.SafeLoader
)
673 or k
.endswith(".gte")
674 or k
.endswith(".lte")
683 elif v
.find(",") > 0:
684 kwargs
[k
] = v
.split(",")
685 elif isinstance(v
, (list, tuple)):
686 for index
in range(0, len(v
)):
691 v
[index
] = yaml
.load(v
[index
], Loader
=yaml
.SafeLoader
)
696 except (ValueError, yaml
.YAMLError
) as exc
:
697 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
698 except KeyError as exc
:
700 "Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
702 except Exception as exc
:
703 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
706 def _format_out(data
, token_info
=None, _format
=None):
708 return string of dictionary data according to requested json, yaml, xml. By default json
709 :param data: response to be sent. Can be a dict, text or file
710 :param token_info: Contains among other username and project
711 :param _format: The format to be set as Content-Type if data is a file
714 accept
= cherrypy
.request
.headers
.get("Accept")
716 if accept
and "text/html" in accept
:
718 data
, cherrypy
.request
, cherrypy
.response
, token_info
720 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
722 elif hasattr(data
, "read"): # file object
724 cherrypy
.response
.headers
["Content-Type"] = _format
725 elif "b" in data
.mode
: # binariy asssumig zip
726 cherrypy
.response
.headers
["Content-Type"] = "application/zip"
728 cherrypy
.response
.headers
["Content-Type"] = "text/plain"
729 # TODO check that cherrypy close file. If not implement pending things to close per thread next
732 if "text/html" in accept
:
734 data
, cherrypy
.request
, cherrypy
.response
, token_info
736 elif "application/yaml" in accept
or "*/*" in accept
:
738 elif "application/json" in accept
or (
739 cherrypy
.response
.status
and cherrypy
.response
.status
>= 300
741 cherrypy
.response
.headers
[
743 ] = "application/json; charset=utf-8"
744 a
= json
.dumps(data
, indent
=4) + "\n"
745 return a
.encode("utf8")
746 cherrypy
.response
.headers
["Content-Type"] = "application/yaml"
747 return yaml
.safe_dump(
751 default_flow_style
=False,
755 ) # , canonical=True, default_style='"'
758 def index(self
, *args
, **kwargs
):
761 if cherrypy
.request
.method
== "GET":
762 token_info
= self
.authenticator
.authorize()
763 outdata
= token_info
# Home page
765 raise cherrypy
.HTTPError(
766 HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
767 "Method {} not allowed for tokens".format(cherrypy
.request
.method
),
770 return self
._format
_out
(outdata
, token_info
)
772 except (EngineException
, AuthException
) as e
:
773 # cherrypy.log("index Exception {}".format(e))
774 cherrypy
.response
.status
= e
.http_code
.value
775 return self
._format
_out
("Welcome to OSM!", token_info
)
778 def version(self
, *args
, **kwargs
):
779 # TODO consider to remove and provide version using the static version file
781 if cherrypy
.request
.method
!= "GET":
783 "Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
787 "Invalid URL or query string for version",
788 HTTPStatus
.METHOD_NOT_ALLOWED
,
790 # TODO include version of other modules, pick up from some kafka admin message
791 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
792 return self
._format
_out
(osm_nbi_version
)
793 except NbiException
as e
:
794 cherrypy
.response
.status
= e
.http_code
.value
796 "code": e
.http_code
.name
,
797 "status": e
.http_code
.value
,
800 return self
._format
_out
(problem_details
, None)
805 "user_domain_name": cherrypy
.tree
.apps
["/osm"]
806 .config
["authentication"]
807 .get("user_domain_name"),
808 "project_domain_name": cherrypy
.tree
.apps
["/osm"]
809 .config
["authentication"]
810 .get("project_domain_name"),
812 return self
._format
_out
(domains
)
813 except NbiException
as e
:
814 cherrypy
.response
.status
= e
.http_code
.value
816 "code": e
.http_code
.name
,
817 "status": e
.http_code
.value
,
820 return self
._format
_out
(problem_details
, None)
823 def _format_login(token_info
):
825 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
827 :param token_info: Dictionary with token content
830 cherrypy
.request
.login
= token_info
.get("username", "-")
831 if token_info
.get("project_name"):
832 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
833 if token_info
.get("id"):
834 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
836 # NS Fault Management
838 def nsfm(self
, version
=None, topic
=None, uuid
=None, project_name
=None, ns_id
=None, *args
, **kwargs
):
839 if topic
== 'alarms':
841 method
= cherrypy
.request
.method
842 role_permission
= self
._check
_valid
_url
_method
(method
, "nsfm", version
, topic
, None, None, *args
)
843 query_string_operations
= self
._extract
_query
_string
_operations
(kwargs
, method
)
845 self
.authenticator
.authorize(role_permission
, query_string_operations
, None)
847 # to handle get request
848 if cherrypy
.request
.method
== 'GET':
849 # if request is on basis of uuid
850 if uuid
and uuid
!= 'None':
852 alarm
= self
.engine
.db
.get_one("alarms", {"uuid": uuid
})
853 alarm_action
= self
.engine
.db
.get_one("alarms_action", {"uuid": uuid
})
854 alarm
.update(alarm_action
)
855 vnf
= self
.engine
.db
.get_one("vnfrs", {"nsr-id-ref": alarm
["tags"]["ns_id"]})
856 alarm
["vnf-id"] = vnf
["_id"]
857 return self
._format
_out
(str(alarm
))
859 return self
._format
_out
("Please provide valid alarm uuid")
860 elif ns_id
and ns_id
!= 'None':
861 # if request is on basis of ns_id
863 alarms
= self
.engine
.db
.get_list("alarms", {"tags.ns_id": ns_id
})
865 alarm_action
= self
.engine
.db
.get_one("alarms_action", {"uuid": alarm
['uuid']})
866 alarm
.update(alarm_action
)
867 return self
._format
_out
(str(alarms
))
869 return self
._format
_out
("Please provide valid ns id")
871 # to return only alarm which are related to given project
872 project
= self
.engine
.db
.get_one("projects", {"name": project_name
})
873 project_id
= project
.get('_id')
874 ns_list
= self
.engine
.db
.get_list("nsrs", {"_admin.projects_read": project_id
})
877 ns_ids
.append(ns
.get("_id"))
878 alarms
= self
.engine
.db
.get_list("alarms")
879 alarm_list
= [alarm
for alarm
in alarms
if alarm
["tags"]["ns_id"] in ns_ids
]
880 for alrm
in alarm_list
:
881 action
= self
.engine
.db
.get_one("alarms_action", {"uuid": alrm
.get("uuid")})
883 return self
._format
_out
(str(alarm_list
))
884 # to handle patch request for alarm update
885 elif cherrypy
.request
.method
== 'PATCH':
886 data
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
888 # check if uuid is valid
889 self
.engine
.db
.get_one("alarms", {"uuid": data
.get("uuid")})
891 return self
._format
_out
("Please provide valid alarm uuid.")
892 if data
.get("is_enable") is not None:
893 if data
.get("is_enable"):
896 alarm_status
= 'disabled'
897 self
.engine
.db
.set_one("alarms", {"uuid": data
.get("uuid")},
898 {"alarm_status": alarm_status
})
900 self
.engine
.db
.set_one("alarms", {"uuid": data
.get("uuid")},
901 {"threshold": data
.get("threshold")})
902 return self
._format
_out
("Alarm updated")
903 except Exception as e
:
904 cherrypy
.response
.status
= e
.http_code
.value
905 if isinstance(e
, (NbiException
, EngineException
, DbException
, FsException
, MsgException
, AuthException
,
906 ValidationError
, AuthconnException
)):
907 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
908 http_code_name
= e
.http_code
.name
909 cherrypy
.log("Exception {}".format(e
))
911 http_code_value
= cherrypy
.response
.status
= HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
912 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
913 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
915 "code": http_code_name
,
916 "status": http_code_value
,
919 return self
._format
_out
(problem_details
)
922 def token(self
, method
, token_id
=None, kwargs
=None):
924 # self.engine.load_dbase(cherrypy.request.app.config)
925 indata
= self
._format
_in
(kwargs
)
926 if not isinstance(indata
, dict):
928 "Expected application/yaml or application/json Content-Type",
929 HTTPStatus
.BAD_REQUEST
,
933 token_info
= self
.authenticator
.authorize()
935 self
._format
_login
(token_info
)
937 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
939 outdata
= self
.authenticator
.get_token_list(token_info
)
940 elif method
== "POST":
942 token_info
= self
.authenticator
.authorize()
946 indata
.update(kwargs
)
947 # This is needed to log the user when authentication fails
948 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
949 outdata
= token_info
= self
.authenticator
.new_token(
950 token_info
, indata
, cherrypy
.request
.remote
952 cherrypy
.session
["Authorization"] = outdata
["_id"]
953 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
955 self
._format
_login
(token_info
)
956 # password expiry check
957 if self
.authenticator
.check_password_expiry(outdata
):
958 outdata
= {"id": outdata
["id"],
959 "message": "change_password",
960 "user_id": outdata
["user_id"]
962 # cherrypy.response.cookie["Authorization"] = outdata["id"]
963 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
964 elif method
== "DELETE":
965 if not token_id
and "id" in kwargs
:
966 token_id
= kwargs
["id"]
968 token_info
= self
.authenticator
.authorize()
970 self
._format
_login
(token_info
)
971 token_id
= token_info
["_id"]
972 outdata
= self
.authenticator
.del_token(token_id
)
974 cherrypy
.session
["Authorization"] = "logout"
975 # cherrypy.response.cookie["Authorization"] = token_id
976 # cherrypy.response.cookie["Authorization"]['expires'] = 0
979 "Method {} not allowed for token".format(method
),
980 HTTPStatus
.METHOD_NOT_ALLOWED
,
982 return self
._format
_out
(outdata
, token_info
)
985 def test(self
, *args
, **kwargs
):
986 if not cherrypy
.config
.get("server.enable_test") or (
987 isinstance(cherrypy
.config
["server.enable_test"], str)
988 and cherrypy
.config
["server.enable_test"].lower() == "false"
990 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
991 return "test URL is disabled"
993 if args
and args
[0] == "help":
995 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
996 "sleep/<time>\nmessage/topic\n</pre></html>"
999 elif args
and args
[0] == "init":
1001 # self.engine.load_dbase(cherrypy.request.app.config)
1002 self
.engine
.create_admin()
1003 return "Done. User 'admin', password 'admin' created"
1005 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1006 return self
._format
_out
("Database already initialized")
1007 elif args
and args
[0] == "file":
1008 return cherrypy
.lib
.static
.serve_file(
1009 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1],
1013 elif args
and args
[0] == "file2":
1015 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1]
1017 f
= open(f_path
, "r")
1018 cherrypy
.response
.headers
["Content-type"] = "text/plain"
1021 elif len(args
) == 2 and args
[0] == "db-clear":
1022 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
1023 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
1024 elif len(args
) and args
[0] == "fs-clear":
1026 folders
= (args
[1],)
1028 folders
= self
.engine
.fs
.dir_ls(".")
1029 for folder
in folders
:
1030 self
.engine
.fs
.file_delete(folder
)
1031 return ",".join(folders
) + " folders deleted\n"
1032 elif args
and args
[0] == "login":
1033 if not cherrypy
.request
.headers
.get("Authorization"):
1034 cherrypy
.response
.headers
[
1036 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
1037 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1038 elif args
and args
[0] == "login2":
1039 if not cherrypy
.request
.headers
.get("Authorization"):
1040 cherrypy
.response
.headers
[
1042 ] = 'Bearer realm="Access to OSM site"'
1043 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1044 elif args
and args
[0] == "sleep":
1047 sleep_time
= int(args
[1])
1049 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1050 return self
._format
_out
("Database already initialized")
1051 thread_info
= cherrypy
.thread_data
1053 time
.sleep(sleep_time
)
1055 elif len(args
) >= 2 and args
[0] == "message":
1056 main_topic
= args
[1]
1057 return_text
= "<html><pre>{} ->\n".format(main_topic
)
1059 if cherrypy
.request
.method
== "POST":
1060 to_send
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
1061 for k
, v
in to_send
.items():
1062 self
.engine
.msg
.write(main_topic
, k
, v
)
1063 return_text
+= " {}: {}\n".format(k
, v
)
1064 elif cherrypy
.request
.method
== "GET":
1065 for k
, v
in kwargs
.items():
1066 v_dict
= yaml
.load(v
, Loader
=yaml
.SafeLoader
)
1067 self
.engine
.msg
.write(main_topic
, k
, v_dict
)
1068 return_text
+= " {}: {}\n".format(k
, v_dict
)
1069 except Exception as e
:
1070 return_text
+= "Error: " + str(e
)
1071 return_text
+= "</pre></html>\n"
1075 "<html><pre>\nheaders:\n args: {}\n".format(args
)
1076 + " kwargs: {}\n".format(kwargs
)
1077 + " headers: {}\n".format(cherrypy
.request
.headers
)
1078 + " path_info: {}\n".format(cherrypy
.request
.path_info
)
1079 + " query_string: {}\n".format(cherrypy
.request
.query_string
)
1080 + " session: {}\n".format(cherrypy
.session
)
1081 + " cookie: {}\n".format(cherrypy
.request
.cookie
)
1082 + " method: {}\n".format(cherrypy
.request
.method
)
1083 + " session: {}\n".format(cherrypy
.session
.get("fieldname"))
1086 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
1087 if cherrypy
.request
.body
.length
:
1088 return_text
+= " content: {}\n".format(
1090 cherrypy
.request
.body
.read(
1091 int(cherrypy
.request
.headers
.get("Content-Length", 0))
1096 return_text
+= "thread: {}\n".format(thread_info
)
1097 return_text
+= "</pre></html>"
1101 def _check_valid_url_method(method
, *args
):
1104 "URL must contain at least 'main_topic/version/topic'",
1105 HTTPStatus
.METHOD_NOT_ALLOWED
,
1108 reference
= valid_url_methods
1112 if not isinstance(reference
, dict):
1114 "URL contains unexpected extra items '{}'".format(arg
),
1115 HTTPStatus
.METHOD_NOT_ALLOWED
,
1118 if arg
in reference
:
1119 reference
= reference
[arg
]
1120 elif "<ID>" in reference
:
1121 reference
= reference
["<ID>"]
1122 elif "*" in reference
:
1123 # if there is content
1125 reference
= reference
["*"]
1129 "Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
1131 if "TODO" in reference
and method
in reference
["TODO"]:
1133 "Method {} not supported yet for this URL".format(method
),
1134 HTTPStatus
.NOT_IMPLEMENTED
,
1136 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
1138 "Method {} not supported for this URL".format(method
),
1139 HTTPStatus
.METHOD_NOT_ALLOWED
,
1141 return reference
["ROLE_PERMISSION"] + method
.lower()
1144 def _set_location_header(main_topic
, version
, topic
, id):
1146 Insert response header Location with the URL of created item base on URL params
1153 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1154 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(
1155 main_topic
, version
, topic
, id
1160 def _extract_query_string_operations(kwargs
, method
):
1166 query_string_operations
= []
1168 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1169 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
1170 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
1171 return query_string_operations
1174 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
1176 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1177 Check that users has rights to use them and returs the admin_query
1178 :param token_info: token_info rights obtained by token
1179 :param kwargs: query string input.
1180 :param method: http method: GET, POSST, PUT, ...
1182 :return: admin_query dictionary with keys:
1183 public: True, False or None
1184 force: True or False
1185 project_id: tuple with projects used for accessing an element
1186 set_project: tuple with projects that a created element will belong to
1187 method: show, list, delete, write
1191 "project_id": (token_info
["project_id"],),
1192 "username": token_info
["username"],
1193 "admin": token_info
["admin"],
1195 "allow_show_user_project_role": token_info
["allow_show_user_project_role"],
1199 if "FORCE" in kwargs
:
1201 kwargs
["FORCE"].lower() != "false"
1202 ): # if None or True set force to True
1203 admin_query
["force"] = True
1206 if "PUBLIC" in kwargs
:
1208 kwargs
["PUBLIC"].lower() != "false"
1209 ): # if None or True set public to True
1210 admin_query
["public"] = True
1212 admin_query
["public"] = False
1213 del kwargs
["PUBLIC"]
1215 if "ADMIN" in kwargs
:
1216 behave_as
= kwargs
.pop("ADMIN")
1217 if behave_as
.lower() != "false":
1218 if not token_info
["admin"]:
1220 "Only admin projects can use 'ADMIN' query string",
1221 HTTPStatus
.UNAUTHORIZED
,
1224 not behave_as
or behave_as
.lower() == "true"
1225 ): # convert True, None to empty list
1226 admin_query
["project_id"] = ()
1227 elif isinstance(behave_as
, (list, tuple)):
1228 admin_query
["project_id"] = behave_as
1229 else: # isinstance(behave_as, str)
1230 admin_query
["project_id"] = (behave_as
,)
1231 if "SET_PROJECT" in kwargs
:
1232 set_project
= kwargs
.pop("SET_PROJECT")
1234 admin_query
["set_project"] = list(admin_query
["project_id"])
1236 if isinstance(set_project
, str):
1237 set_project
= (set_project
,)
1238 if admin_query
["project_id"]:
1239 for p
in set_project
:
1240 if p
not in admin_query
["project_id"]:
1242 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1243 "'ADMIN='{p}'".format(p
=p
),
1244 HTTPStatus
.UNAUTHORIZED
,
1246 admin_query
["set_project"] = set_project
1249 # if "PROJECT_READ" in kwargs:
1250 # admin_query["project"] = kwargs.pop("project")
1251 # if admin_query["project"] == token_info["project_id"]:
1254 admin_query
["method"] = "show"
1256 admin_query
["method"] = "list"
1257 elif method
== "DELETE":
1258 admin_query
["method"] = "delete"
1260 admin_query
["method"] = "write"
1280 engine_session
= None
1282 if not main_topic
or not version
or not topic
:
1284 "URL must contain at least 'main_topic/version/topic'",
1285 HTTPStatus
.METHOD_NOT_ALLOWED
,
1287 if main_topic
not in (
1298 "URL main_topic '{}' not supported".format(main_topic
),
1299 HTTPStatus
.METHOD_NOT_ALLOWED
,
1303 "URL version '{}' not supported".format(version
),
1304 HTTPStatus
.METHOD_NOT_ALLOWED
,
1309 and "METHOD" in kwargs
1310 and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1312 method
= kwargs
.pop("METHOD")
1314 method
= cherrypy
.request
.method
1316 role_permission
= self
._check
_valid
_url
_method
(
1317 method
, main_topic
, version
, topic
, _id
, item
, *args
1319 query_string_operations
= self
._extract
_query
_string
_operations
(
1322 if main_topic
== "admin" and topic
== "tokens":
1323 return self
.token(method
, _id
, kwargs
)
1324 token_info
= self
.authenticator
.authorize(
1325 role_permission
, query_string_operations
, _id
1327 if main_topic
== "admin" and topic
== "domains":
1328 return self
.domain()
1329 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
1330 indata
= self
._format
_in
(kwargs
)
1331 engine_topic
= topic
1333 if item
and topic
!= "pm_jobs":
1336 if main_topic
== "nsd":
1337 engine_topic
= "nsds"
1338 elif main_topic
== "vnfpkgm":
1339 engine_topic
= "vnfds"
1340 if topic
== "vnfpkg_op_occs":
1341 engine_topic
= "vnfpkgops"
1342 if topic
== "vnf_packages" and item
== "action":
1343 engine_topic
= "vnfpkgops"
1344 elif main_topic
== "nslcm":
1345 engine_topic
= "nsrs"
1346 if topic
== "ns_lcm_op_occs":
1347 engine_topic
= "nslcmops"
1348 if topic
== "vnfrs" or topic
== "vnf_instances":
1349 engine_topic
= "vnfrs"
1350 elif main_topic
== "nst":
1351 engine_topic
= "nsts"
1352 elif main_topic
== "nsilcm":
1353 engine_topic
= "nsis"
1354 if topic
== "nsi_lcm_op_occs":
1355 engine_topic
= "nsilcmops"
1356 elif main_topic
== "pdu":
1357 engine_topic
= "pdus"
1359 engine_topic
== "vims"
1360 ): # TODO this is for backward compatibility, it will be removed in the future
1361 engine_topic
= "vim_accounts"
1363 if topic
== "subscriptions":
1364 engine_topic
= main_topic
+ "_" + topic
1376 if item
in ("vnfd", "nsd", "nst"):
1377 path
= "$DESCRIPTOR"
1380 elif item
== "artifacts":
1384 file, _format
= self
.engine
.get_file(
1389 cherrypy
.request
.headers
.get("Accept"),
1393 outdata
= self
.engine
.get_item_list(
1394 engine_session
, engine_topic
, kwargs
, api_req
=True
1397 if item
== "reports":
1398 # TODO check that project_id (_id in this context) has permissions
1401 if "vcaStatusRefresh" in kwargs
:
1402 filter_q
= {"vcaStatusRefresh": kwargs
["vcaStatusRefresh"]}
1403 outdata
= self
.engine
.get_item(engine_session
, engine_topic
, _id
, filter_q
, True)
1405 elif method
== "POST":
1406 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1408 "ns_descriptors_content",
1409 "vnf_packages_content",
1410 "netslice_templates_content",
1412 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1414 _id
, _
= self
.engine
.new_item(
1420 cherrypy
.request
.headers
,
1422 completed
= self
.engine
.upload_content(
1428 cherrypy
.request
.headers
,
1431 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1433 cherrypy
.response
.headers
["Transaction-Id"] = _id
1434 outdata
= {"id": _id
}
1435 elif topic
== "ns_instances_content":
1437 _id
, _
= self
.engine
.new_item(
1438 rollback
, engine_session
, engine_topic
, indata
, kwargs
1441 indata
["lcmOperationType"] = "instantiate"
1442 indata
["nsInstanceId"] = _id
1443 nslcmop_id
, _
= self
.engine
.new_item(
1444 rollback
, engine_session
, "nslcmops", indata
, None
1446 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1447 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1448 elif topic
== "ns_instances" and item
:
1449 indata
["lcmOperationType"] = item
1450 indata
["nsInstanceId"] = _id
1451 _id
, _
= self
.engine
.new_item(
1452 rollback
, engine_session
, "nslcmops", indata
, kwargs
1454 self
._set
_location
_header
(
1455 main_topic
, version
, "ns_lcm_op_occs", _id
1457 outdata
= {"id": _id
}
1458 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1459 elif topic
== "netslice_instances_content":
1460 # creates NetSlice_Instance_record (NSIR)
1461 _id
, _
= self
.engine
.new_item(
1462 rollback
, engine_session
, engine_topic
, indata
, kwargs
1464 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1465 indata
["lcmOperationType"] = "instantiate"
1466 indata
["netsliceInstanceId"] = _id
1467 nsilcmop_id
, _
= self
.engine
.new_item(
1468 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1470 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1471 elif topic
== "netslice_instances" and item
:
1472 indata
["lcmOperationType"] = item
1473 indata
["netsliceInstanceId"] = _id
1474 _id
, _
= self
.engine
.new_item(
1475 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1477 self
._set
_location
_header
(
1478 main_topic
, version
, "nsi_lcm_op_occs", _id
1480 outdata
= {"id": _id
}
1481 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1482 elif topic
== "vnf_packages" and item
== "action":
1483 indata
["lcmOperationType"] = item
1484 indata
["vnfPkgId"] = _id
1485 _id
, _
= self
.engine
.new_item(
1486 rollback
, engine_session
, "vnfpkgops", indata
, kwargs
1488 self
._set
_location
_header
(
1489 main_topic
, version
, "vnfpkg_op_occs", _id
1491 outdata
= {"id": _id
}
1492 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1493 elif topic
== "subscriptions":
1494 _id
, _
= self
.engine
.new_item(
1495 rollback
, engine_session
, engine_topic
, indata
, kwargs
1497 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1499 link
["self"] = cherrypy
.response
.headers
["Location"]
1502 "filter": indata
["filter"],
1503 "callbackUri": indata
["CallbackUri"],
1506 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1508 _id
, op_id
= self
.engine
.new_item(
1514 cherrypy
.request
.headers
,
1516 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1517 outdata
= {"id": _id
}
1519 outdata
["op_id"] = op_id
1520 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1521 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1523 elif method
== "DELETE":
1525 outdata
= self
.engine
.del_item_list(
1526 engine_session
, engine_topic
, kwargs
1528 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1529 else: # len(args) > 1
1530 # for NS NSI generate an operation
1532 if topic
== "ns_instances_content" and not engine_session
["force"]:
1534 "lcmOperationType": "terminate",
1535 "nsInstanceId": _id
,
1538 op_id
, _
= self
.engine
.new_item(
1539 rollback
, engine_session
, "nslcmops", nslcmop_desc
, kwargs
1542 outdata
= {"_id": op_id
}
1544 topic
== "netslice_instances_content"
1545 and not engine_session
["force"]
1548 "lcmOperationType": "terminate",
1549 "netsliceInstanceId": _id
,
1552 op_id
, _
= self
.engine
.new_item(
1553 rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None
1556 outdata
= {"_id": op_id
}
1557 # if there is not any deletion in process, delete
1559 op_id
= self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1561 outdata
= {"op_id": op_id
}
1562 cherrypy
.response
.status
= (
1563 HTTPStatus
.ACCEPTED
.value
1565 else HTTPStatus
.NO_CONTENT
.value
1568 elif method
in ("PUT", "PATCH"):
1570 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1572 "Nothing to update. Provide payload and/or query string",
1573 HTTPStatus
.BAD_REQUEST
,
1576 item
in ("nsd_content", "package_content", "nst_content")
1579 completed
= self
.engine
.upload_content(
1585 cherrypy
.request
.headers
,
1588 cherrypy
.response
.headers
["Transaction-Id"] = id
1590 op_id
= self
.engine
.edit_item(
1591 engine_session
, engine_topic
, _id
, indata
, kwargs
1595 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1596 outdata
= {"op_id": op_id
}
1598 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1602 "Method {} not allowed".format(method
),
1603 HTTPStatus
.METHOD_NOT_ALLOWED
,
1606 # if Role information changes, it is needed to reload the information of roles
1607 if topic
== "roles" and method
!= "GET":
1608 self
.authenticator
.load_operation_to_allowed_roles()
1612 and method
== "DELETE"
1613 or topic
in ["users", "roles"]
1614 and method
in ["PUT", "PATCH", "DELETE"]
1616 self
.authenticator
.remove_token_from_cache()
1618 return self
._format
_out
(outdata
, token_info
, _format
)
1619 except Exception as e
:
1633 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1634 http_code_name
= e
.http_code
.name
1635 cherrypy
.log("Exception {}".format(e
))
1638 cherrypy
.response
.status
1639 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1640 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1641 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1642 if hasattr(outdata
, "close"): # is an open file
1646 for rollback_item
in rollback
:
1648 if rollback_item
.get("operation") == "set":
1649 self
.engine
.db
.set_one(
1650 rollback_item
["topic"],
1651 {"_id": rollback_item
["_id"]},
1652 rollback_item
["content"],
1653 fail_on_empty
=False,
1655 elif rollback_item
.get("operation") == "del_list":
1656 self
.engine
.db
.del_list(
1657 rollback_item
["topic"],
1658 rollback_item
["filter"],
1659 fail_on_empty
=False,
1662 self
.engine
.db
.del_one(
1663 rollback_item
["topic"],
1664 {"_id": rollback_item
["_id"]},
1665 fail_on_empty
=False,
1667 except Exception as e2
:
1668 rollback_error_text
= "Rollback Exception {}: {}".format(
1671 cherrypy
.log(rollback_error_text
)
1672 error_text
+= ". " + rollback_error_text
1673 # if isinstance(e, MsgException):
1674 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1675 # engine_topic[:-1], method, error_text)
1677 "code": http_code_name
,
1678 "status": http_code_value
,
1679 "detail": error_text
,
1681 return self
._format
_out
(problem_details
, token_info
)
1682 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1685 self
._format
_login
(token_info
)
1686 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1687 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1688 if outdata
.get(logging_id
):
1689 cherrypy
.request
.login
+= ";{}={}".format(
1690 logging_id
, outdata
[logging_id
][:36]
1694 def _start_service():
1696 Callback function called when cherrypy.engine starts
1697 Override configuration with env variables
1698 Set database, storage, message configuration
1699 Init database with admin/admin user password
1702 global subscription_thread
1703 cherrypy
.log
.error("Starting osm_nbi")
1704 # update general cherrypy configuration
1707 engine_config
= cherrypy
.tree
.apps
["/osm"].config
1708 for k
, v
in environ
.items():
1709 if not k
.startswith("OSMNBI_"):
1711 k1
, _
, k2
= k
[7:].lower().partition("_")
1715 # update static configuration
1716 if k
== "OSMNBI_STATIC_DIR":
1717 engine_config
["/static"]["tools.staticdir.dir"] = v
1718 engine_config
["/static"]["tools.staticdir.on"] = True
1719 elif k
== "OSMNBI_SOCKET_PORT" or k
== "OSMNBI_SERVER_PORT":
1720 update_dict
["server.socket_port"] = int(v
)
1721 elif k
== "OSMNBI_SOCKET_HOST" or k
== "OSMNBI_SERVER_HOST":
1722 update_dict
["server.socket_host"] = v
1723 elif k1
in ("server", "test", "auth", "log"):
1724 update_dict
[k1
+ "." + k2
] = v
1725 elif k1
in ("message", "database", "storage", "authentication"):
1726 # k2 = k2.replace('_', '.')
1727 if k2
in ("port", "db_port"):
1728 engine_config
[k1
][k2
] = int(v
)
1730 engine_config
[k1
][k2
] = v
1732 except ValueError as e
:
1733 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1734 except Exception as e
:
1735 cherrypy
.log
.warn("skipping environ '{}' on exception '{}'".format(k
, e
))
1738 cherrypy
.config
.update(update_dict
)
1739 engine_config
["global"].update(update_dict
)
1742 log_format_simple
= (
1743 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1745 log_formatter_simple
= logging
.Formatter(
1746 log_format_simple
, datefmt
="%Y-%m-%dT%H:%M:%S"
1748 logger_server
= logging
.getLogger("cherrypy.error")
1749 logger_access
= logging
.getLogger("cherrypy.access")
1750 logger_cherry
= logging
.getLogger("cherrypy")
1751 logger_nbi
= logging
.getLogger("nbi")
1753 if "log.file" in engine_config
["global"]:
1754 file_handler
= logging
.handlers
.RotatingFileHandler(
1755 engine_config
["global"]["log.file"], maxBytes
=100e6
, backupCount
=9, delay
=0
1757 file_handler
.setFormatter(log_formatter_simple
)
1758 logger_cherry
.addHandler(file_handler
)
1759 logger_nbi
.addHandler(file_handler
)
1760 # log always to standard output
1761 for format_
, logger
in {
1762 "nbi.server %(filename)s:%(lineno)s": logger_server
,
1763 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1764 "%(name)s %(filename)s:%(lineno)s": logger_nbi
,
1766 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1767 log_formatter_cherry
= logging
.Formatter(
1768 log_format_cherry
, datefmt
="%Y-%m-%dT%H:%M:%S"
1770 str_handler
= logging
.StreamHandler()
1771 str_handler
.setFormatter(log_formatter_cherry
)
1772 logger
.addHandler(str_handler
)
1774 if engine_config
["global"].get("log.level"):
1775 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1776 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1778 # logging other modules
1779 for k1
, logname
in {
1780 "message": "nbi.msg",
1781 "database": "nbi.db",
1782 "storage": "nbi.fs",
1784 engine_config
[k1
]["logger_name"] = logname
1785 logger_module
= logging
.getLogger(logname
)
1786 if "logfile" in engine_config
[k1
]:
1787 file_handler
= logging
.handlers
.RotatingFileHandler(
1788 engine_config
[k1
]["logfile"], maxBytes
=100e6
, backupCount
=9, delay
=0
1790 file_handler
.setFormatter(log_formatter_simple
)
1791 logger_module
.addHandler(file_handler
)
1792 if "loglevel" in engine_config
[k1
]:
1793 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1794 # TODO add more entries, e.g.: storage
1795 cherrypy
.tree
.apps
["/osm"].root
.engine
.start(engine_config
)
1796 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.start(engine_config
)
1797 cherrypy
.tree
.apps
["/osm"].root
.engine
.init_db(target_version
=database_version
)
1798 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.init_db(
1799 target_version
=auth_database_version
1802 # start subscriptions thread:
1803 subscription_thread
= SubscriptionThread(
1804 config
=engine_config
, engine
=nbi_server
.engine
1806 subscription_thread
.start()
1807 # Do not capture except SubscriptionException
1809 backend
= engine_config
["authentication"]["backend"]
1811 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1812 nbi_version
, nbi_version_date
, backend
1817 def _stop_service():
1819 Callback function called when cherrypy.engine stops
1820 TODO: Ending database connections.
1822 global subscription_thread
1823 if subscription_thread
:
1824 subscription_thread
.terminate()
1825 subscription_thread
= None
1826 cherrypy
.tree
.apps
["/osm"].root
.engine
.stop()
1827 cherrypy
.log
.error("Stopping osm_nbi")
1830 def nbi(config_file
):
1834 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1835 # 'tools.sessions.on': True,
1836 # 'tools.response_headers.on': True,
1837 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1840 # cherrypy.Server.ssl_module = 'builtin'
1841 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1842 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1843 # cherrypy.Server.thread_pool = 10
1844 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1846 # cherrypy.config.update({'tools.auth_basic.on': True,
1847 # 'tools.auth_basic.realm': 'localhost',
1848 # 'tools.auth_basic.checkpassword': validate_password})
1849 nbi_server
= Server()
1850 cherrypy
.engine
.subscribe("start", _start_service
)
1851 cherrypy
.engine
.subscribe("stop", _stop_service
)
1852 cherrypy
.quickstart(nbi_server
, "/osm", config_file
)
1857 """Usage: {} [options]
1858 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1859 -h|--help: shows this help
1864 # --log-socket-host HOST: send logs to this host")
1865 # --log-socket-port PORT: send logs using this port (default: 9022)")
1868 if __name__
== "__main__":
1870 # load parameters and configuration
1871 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
1872 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1875 if o
in ("-h", "--help"):
1878 elif o
in ("-c", "--config"):
1880 # elif o == "--log-socket-port":
1881 # log_socket_port = a
1882 # elif o == "--log-socket-host":
1883 # log_socket_host = a
1884 # elif o == "--log-file":
1887 assert False, "Unhandled option"
1889 if not path
.isfile(config_file
):
1891 "configuration file '{}' that not exist".format(config_file
),
1896 for config_file
in (
1897 __file__
[: __file__
.rfind(".")] + ".cfg",
1901 if path
.isfile(config_file
):
1905 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
1910 except getopt
.GetoptError
as e
:
1911 print(str(e
), file=sys
.stderr
)