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
93 /vnf_instances (also vnfrs for compatibility) O
109 /vim_accounts (also vims for compatibility) O O
123 /netslice_templates_content O O
125 /netslice_templates O O
129 /artifacts[/<artifactPath>] O
131 /<subscriptionId> X X
134 /netslice_instances_content O O
135 /<SliceInstanceId> O O
136 /netslice_instances O O
137 /<SliceInstanceId> O O
142 /<nsiLcmOpOccId> O O O
144 /<subscriptionId> X X
147 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
148 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
149 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
150 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
152 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
153 item of the array, that is, pass if any item of the array pass the filter.
154 It allows both ne and neq for not equal
155 TODO: 4.3.3 Attribute selectors
156 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
157 (none) … same as “exclude_default”
158 all_fields … all attributes.
159 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
160 conditionally mandatory, and that are not provided in <list>.
161 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
162 are not conditionally mandatory, and that are provided in <list>.
163 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
164 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
165 the particular resource
166 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
167 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
168 present specification for the particular resource, but that are not part of <list>
169 Additionally it admits some administrator values:
170 FORCE: To force operations skipping dependency checkings
171 ADMIN: To act as an administrator or a different project
172 PUBLIC: To get public descriptors or set a descriptor as public
173 SET_PROJECT: To make a descriptor available for other project
175 Header field name Reference Example Descriptions
176 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
177 This header field shall be present if the response is expected to have a non-empty message body.
178 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
179 This header field shall be present if the request has a non-empty message body.
180 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
181 Details are specified in clause 4.5.3.
182 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
183 Header field name Reference Example Descriptions
184 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
185 This header field shall be present if the response has a non-empty message body.
186 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
187 new resource has been created.
188 This header field shall be present if the response status code is 201 or 3xx.
189 In the present document this header field is also used if the response status code is 202 and a new resource was
191 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
192 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
194 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
196 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
197 response, and the total length of the file.
198 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
201 valid_query_string
= ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
202 # ^ Contains possible administrative query string words:
203 # ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
204 # (not owned by my session project).
205 # PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
206 # FORCE=True(by default)|False: Force edition/deletion operations
207 # SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
209 valid_url_methods
= {
210 # contains allowed URL and methods, and the role_permission name
214 "METHODS": ("GET", "POST", "DELETE"),
215 "ROLE_PERMISSION": "tokens:",
216 "<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"},
219 "METHODS": ("GET", "POST"),
220 "ROLE_PERMISSION": "users:",
222 "METHODS": ("GET", "DELETE", "PATCH"),
223 "ROLE_PERMISSION": "users:id:",
227 "METHODS": ("GET", "POST"),
228 "ROLE_PERMISSION": "projects:",
230 "METHODS": ("GET", "DELETE", "PATCH"),
231 "ROLE_PERMISSION": "projects:id:",
235 "METHODS": ("GET", "POST"),
236 "ROLE_PERMISSION": "roles:",
238 "METHODS": ("GET", "DELETE", "PATCH"),
239 "ROLE_PERMISSION": "roles:id:",
243 "METHODS": ("GET", "POST"),
244 "ROLE_PERMISSION": "vims:",
246 "METHODS": ("GET", "DELETE", "PATCH"),
247 "ROLE_PERMISSION": "vims:id:",
251 "METHODS": ("GET", "POST"),
252 "ROLE_PERMISSION": "vim_accounts:",
254 "METHODS": ("GET", "DELETE", "PATCH"),
255 "ROLE_PERMISSION": "vim_accounts:id:",
259 "METHODS": ("GET", "POST"),
260 "ROLE_PERMISSION": "wim_accounts:",
262 "METHODS": ("GET", "DELETE", "PATCH"),
263 "ROLE_PERMISSION": "wim_accounts:id:",
267 "METHODS": ("GET", "POST"),
268 "ROLE_PERMISSION": "sdn_controllers:",
270 "METHODS": ("GET", "DELETE", "PATCH"),
271 "ROLE_PERMISSION": "sdn_controllers:id:",
275 "METHODS": ("GET", "POST"),
276 "ROLE_PERMISSION": "k8sclusters:",
278 "METHODS": ("GET", "DELETE", "PATCH"),
279 "ROLE_PERMISSION": "k8sclusters:id:",
283 "METHODS": ("GET", "POST"),
284 "ROLE_PERMISSION": "vca:",
286 "METHODS": ("GET", "DELETE", "PATCH"),
287 "ROLE_PERMISSION": "vca:id:",
291 "METHODS": ("GET", "POST"),
292 "ROLE_PERMISSION": "k8srepos:",
294 "METHODS": ("GET", "DELETE"),
295 "ROLE_PERMISSION": "k8srepos:id:",
299 "METHODS": ("GET", "POST"),
300 "ROLE_PERMISSION": "osmrepos:",
302 "METHODS": ("GET", "DELETE", "PATCH"),
303 "ROLE_PERMISSION": "osmrepos:id:",
308 "ROLE_PERMISSION": "domains:",
315 "METHODS": ("GET", "POST"),
316 "ROLE_PERMISSION": "pduds:",
318 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
319 "ROLE_PERMISSION": "pduds:id:",
326 "ns_descriptors_content": {
327 "METHODS": ("GET", "POST"),
328 "ROLE_PERMISSION": "nsds:",
330 "METHODS": ("GET", "PUT", "DELETE"),
331 "ROLE_PERMISSION": "nsds:id:",
335 "METHODS": ("GET", "POST"),
336 "ROLE_PERMISSION": "nsds:",
338 "METHODS": ("GET", "DELETE", "PATCH"),
339 "ROLE_PERMISSION": "nsds:id:",
341 "METHODS": ("GET", "PUT"),
342 "ROLE_PERMISSION": "nsds:id:content:",
345 "METHODS": ("GET",), # descriptor inside package
346 "ROLE_PERMISSION": "nsds:id:content:",
350 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
356 "TODO": ("GET", "POST"),
358 "TODO": ("GET", "DELETE", "PATCH"),
359 "pnfd_content": {"TODO": ("GET", "PUT")},
363 "TODO": ("GET", "POST"),
364 "<ID>": {"TODO": ("GET", "DELETE")},
370 "vnf_packages_content": {
371 "METHODS": ("GET", "POST"),
372 "ROLE_PERMISSION": "vnfds:",
374 "METHODS": ("GET", "PUT", "DELETE"),
375 "ROLE_PERMISSION": "vnfds:id:",
379 "METHODS": ("GET", "POST"),
380 "ROLE_PERMISSION": "vnfds:",
382 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
383 "ROLE_PERMISSION": "vnfds:id:",
385 "METHODS": ("GET", "PUT"), # package
386 "ROLE_PERMISSION": "vnfds:id:",
390 "ROLE_PERMISSION": "vnfds:id:upload:",
394 "METHODS": ("GET",), # descriptor inside package
395 "ROLE_PERMISSION": "vnfds:id:content:",
399 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
403 "METHODS": ("POST",),
404 "ROLE_PERMISSION": "vnfds:id:action:",
409 "TODO": ("GET", "POST"),
410 "<ID>": {"TODO": ("GET", "DELETE")},
414 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
415 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"},
421 "ns_instances_content": {
422 "METHODS": ("GET", "POST"),
423 "ROLE_PERMISSION": "ns_instances:",
425 "METHODS": ("GET", "DELETE"),
426 "ROLE_PERMISSION": "ns_instances:id:",
430 "METHODS": ("GET", "POST"),
431 "ROLE_PERMISSION": "ns_instances:",
433 "METHODS": ("GET", "DELETE"),
434 "ROLE_PERMISSION": "ns_instances:id:",
436 "METHODS": ("POST",),
437 "ROLE_PERMISSION": "ns_instances:id:scale:",
440 "METHODS": ("POST",),
441 "ROLE_PERMISSION": "ns_instances:id:terminate:",
444 "METHODS": ("POST",),
445 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
448 "METHODS": ("POST",),
449 "ROLE_PERMISSION": "ns_instances:id:action:",
452 "METHODS": ("POST",),
453 "ROLE_PERMISSION": "ns_instances:id:update:",
459 "ROLE_PERMISSION": "ns_instances:opps:",
462 "ROLE_PERMISSION": "ns_instances:opps:id:",
467 "ROLE_PERMISSION": "vnf_instances:",
468 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
472 "ROLE_PERMISSION": "vnf_instances:",
473 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
476 "METHODS": ("GET", "POST"),
477 "ROLE_PERMISSION": "ns_subscriptions:",
479 "METHODS": ("GET", "DELETE"),
480 "ROLE_PERMISSION": "ns_subscriptions:id:",
487 "netslice_templates_content": {
488 "METHODS": ("GET", "POST"),
489 "ROLE_PERMISSION": "slice_templates:",
491 "METHODS": ("GET", "PUT", "DELETE"),
492 "ROLE_PERMISSION": "slice_templates:id:",
495 "netslice_templates": {
496 "METHODS": ("GET", "POST"),
497 "ROLE_PERMISSION": "slice_templates:",
499 "METHODS": ("GET", "DELETE"),
501 "ROLE_PERMISSION": "slice_templates:id:",
503 "METHODS": ("GET", "PUT"),
504 "ROLE_PERMISSION": "slice_templates:id:content:",
507 "METHODS": ("GET",), # descriptor inside package
508 "ROLE_PERMISSION": "slice_templates:id:content:",
512 "ROLE_PERMISSION": "slice_templates:id:content:",
518 "TODO": ("GET", "POST"),
519 "<ID>": {"TODO": ("GET", "DELETE")},
525 "netslice_instances_content": {
526 "METHODS": ("GET", "POST"),
527 "ROLE_PERMISSION": "slice_instances:",
529 "METHODS": ("GET", "DELETE"),
530 "ROLE_PERMISSION": "slice_instances:id:",
533 "netslice_instances": {
534 "METHODS": ("GET", "POST"),
535 "ROLE_PERMISSION": "slice_instances:",
537 "METHODS": ("GET", "DELETE"),
538 "ROLE_PERMISSION": "slice_instances:id:",
540 "METHODS": ("POST",),
541 "ROLE_PERMISSION": "slice_instances:id:terminate:",
544 "METHODS": ("POST",),
545 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
548 "METHODS": ("POST",),
549 "ROLE_PERMISSION": "slice_instances:id:action:",
555 "ROLE_PERMISSION": "slice_instances:opps:",
558 "ROLE_PERMISSION": "slice_instances:opps:id:",
570 "ROLE_PERMISSION": "reports:id:",
579 "alarms": {"METHODS": ("GET", "PATCH"),
580 "ROLE_PERMISSION": "alarms:",
581 "<ID>": {"METHODS": ("GET", "PATCH"),
582 "ROLE_PERMISSION": "alarms:id:",
590 class NbiException(Exception):
591 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
592 Exception.__init
__(self
, message
)
593 self
.http_code
= http_code
596 class Server(object):
598 # to decode bytes to str
599 reader
= getreader("utf-8")
603 self
.authenticator
= Authenticator(valid_url_methods
, valid_query_string
)
604 self
.engine
= Engine(self
.authenticator
)
606 def _format_in(self
, kwargs
):
609 if cherrypy
.request
.body
.length
:
610 error_text
= "Invalid input format "
612 if "Content-Type" in cherrypy
.request
.headers
:
613 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
614 error_text
= "Invalid json format "
615 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
616 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
617 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
618 error_text
= "Invalid yaml format "
620 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
622 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
624 "application/binary" in cherrypy
.request
.headers
["Content-Type"]
625 or "application/gzip"
626 in cherrypy
.request
.headers
["Content-Type"]
627 or "application/zip" in cherrypy
.request
.headers
["Content-Type"]
628 or "text/plain" in cherrypy
.request
.headers
["Content-Type"]
630 indata
= cherrypy
.request
.body
# .read()
632 "multipart/form-data"
633 in cherrypy
.request
.headers
["Content-Type"]
635 if "descriptor_file" in kwargs
:
636 filecontent
= kwargs
.pop("descriptor_file")
637 if not filecontent
.file:
639 "empty file or content", HTTPStatus
.BAD_REQUEST
641 indata
= filecontent
.file # .read()
642 if filecontent
.content_type
.value
:
643 cherrypy
.request
.headers
[
645 ] = filecontent
.content_type
.value
647 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
648 # "Only 'Content-Type' of type 'application/json' or
649 # 'application/yaml' for input format are available")
650 error_text
= "Invalid yaml format "
652 cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
654 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
656 error_text
= "Invalid yaml format "
657 indata
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
658 cherrypy
.request
.headers
.pop("Content-File-MD5", None)
663 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
666 for k
, v
in kwargs
.items():
667 if isinstance(v
, str):
672 kwargs
[k
] = yaml
.load(v
, Loader
=yaml
.SafeLoader
)
678 or k
.endswith(".gte")
679 or k
.endswith(".lte")
688 elif v
.find(",") > 0:
689 kwargs
[k
] = v
.split(",")
690 elif isinstance(v
, (list, tuple)):
691 for index
in range(0, len(v
)):
696 v
[index
] = yaml
.load(v
[index
], Loader
=yaml
.SafeLoader
)
701 except (ValueError, yaml
.YAMLError
) as exc
:
702 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
703 except KeyError as exc
:
705 "Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
707 except Exception as exc
:
708 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
711 def _format_out(data
, token_info
=None, _format
=None):
713 return string of dictionary data according to requested json, yaml, xml. By default json
714 :param data: response to be sent. Can be a dict, text or file
715 :param token_info: Contains among other username and project
716 :param _format: The format to be set as Content-Type if data is a file
719 accept
= cherrypy
.request
.headers
.get("Accept")
721 if accept
and "text/html" in accept
:
723 data
, cherrypy
.request
, cherrypy
.response
, token_info
725 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
727 elif hasattr(data
, "read"): # file object
729 cherrypy
.response
.headers
["Content-Type"] = _format
730 elif "b" in data
.mode
: # binariy asssumig zip
731 cherrypy
.response
.headers
["Content-Type"] = "application/zip"
733 cherrypy
.response
.headers
["Content-Type"] = "text/plain"
734 # TODO check that cherrypy close file. If not implement pending things to close per thread next
737 if "text/html" in accept
:
739 data
, cherrypy
.request
, cherrypy
.response
, token_info
741 elif "application/yaml" in accept
or "*/*" in accept
:
743 elif "application/json" in accept
or (
744 cherrypy
.response
.status
and cherrypy
.response
.status
>= 300
746 cherrypy
.response
.headers
[
748 ] = "application/json; charset=utf-8"
749 a
= json
.dumps(data
, indent
=4) + "\n"
750 return a
.encode("utf8")
751 cherrypy
.response
.headers
["Content-Type"] = "application/yaml"
752 return yaml
.safe_dump(
756 default_flow_style
=False,
760 ) # , canonical=True, default_style='"'
763 def index(self
, *args
, **kwargs
):
766 if cherrypy
.request
.method
== "GET":
767 token_info
= self
.authenticator
.authorize()
768 outdata
= token_info
# Home page
770 raise cherrypy
.HTTPError(
771 HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
772 "Method {} not allowed for tokens".format(cherrypy
.request
.method
),
775 return self
._format
_out
(outdata
, token_info
)
777 except (EngineException
, AuthException
) as e
:
778 # cherrypy.log("index Exception {}".format(e))
779 cherrypy
.response
.status
= e
.http_code
.value
780 return self
._format
_out
("Welcome to OSM!", token_info
)
783 def version(self
, *args
, **kwargs
):
784 # TODO consider to remove and provide version using the static version file
786 if cherrypy
.request
.method
!= "GET":
788 "Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
792 "Invalid URL or query string for version",
793 HTTPStatus
.METHOD_NOT_ALLOWED
,
795 # TODO include version of other modules, pick up from some kafka admin message
796 osm_nbi_version
= {"version": nbi_version
, "date": nbi_version_date
}
797 return self
._format
_out
(osm_nbi_version
)
798 except NbiException
as e
:
799 cherrypy
.response
.status
= e
.http_code
.value
801 "code": e
.http_code
.name
,
802 "status": e
.http_code
.value
,
805 return self
._format
_out
(problem_details
, None)
810 "user_domain_name": cherrypy
.tree
.apps
["/osm"]
811 .config
["authentication"]
812 .get("user_domain_name"),
813 "project_domain_name": cherrypy
.tree
.apps
["/osm"]
814 .config
["authentication"]
815 .get("project_domain_name"),
817 return self
._format
_out
(domains
)
818 except NbiException
as e
:
819 cherrypy
.response
.status
= e
.http_code
.value
821 "code": e
.http_code
.name
,
822 "status": e
.http_code
.value
,
825 return self
._format
_out
(problem_details
, None)
828 def _format_login(token_info
):
830 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
832 :param token_info: Dictionary with token content
835 cherrypy
.request
.login
= token_info
.get("username", "-")
836 if token_info
.get("project_name"):
837 cherrypy
.request
.login
+= "/" + token_info
["project_name"]
838 if token_info
.get("id"):
839 cherrypy
.request
.login
+= ";session=" + token_info
["id"][0:12]
841 # NS Fault Management
843 def nsfm(self
, version
=None, topic
=None, uuid
=None, project_name
=None, ns_id
=None, *args
, **kwargs
):
844 if topic
== 'alarms':
846 method
= cherrypy
.request
.method
847 role_permission
= self
._check
_valid
_url
_method
(method
, "nsfm", version
, topic
, None, None, *args
)
848 query_string_operations
= self
._extract
_query
_string
_operations
(kwargs
, method
)
850 self
.authenticator
.authorize(role_permission
, query_string_operations
, None)
852 # to handle get request
853 if cherrypy
.request
.method
== 'GET':
854 # if request is on basis of uuid
855 if uuid
and uuid
!= 'None':
857 alarm
= self
.engine
.db
.get_one("alarms", {"uuid": uuid
})
858 alarm_action
= self
.engine
.db
.get_one("alarms_action", {"uuid": uuid
})
859 alarm
.update(alarm_action
)
860 vnf
= self
.engine
.db
.get_one("vnfrs", {"nsr-id-ref": alarm
["tags"]["ns_id"]})
861 alarm
["vnf-id"] = vnf
["_id"]
862 return self
._format
_out
(str(alarm
))
864 return self
._format
_out
("Please provide valid alarm uuid")
865 elif ns_id
and ns_id
!= 'None':
866 # if request is on basis of ns_id
868 alarms
= self
.engine
.db
.get_list("alarms", {"tags.ns_id": ns_id
})
870 alarm_action
= self
.engine
.db
.get_one("alarms_action", {"uuid": alarm
['uuid']})
871 alarm
.update(alarm_action
)
872 return self
._format
_out
(str(alarms
))
874 return self
._format
_out
("Please provide valid ns id")
876 # to return only alarm which are related to given project
877 project
= self
.engine
.db
.get_one("projects", {"name": project_name
})
878 project_id
= project
.get('_id')
879 ns_list
= self
.engine
.db
.get_list("nsrs", {"_admin.projects_read": project_id
})
882 ns_ids
.append(ns
.get("_id"))
883 alarms
= self
.engine
.db
.get_list("alarms")
884 alarm_list
= [alarm
for alarm
in alarms
if alarm
["tags"]["ns_id"] in ns_ids
]
885 for alrm
in alarm_list
:
886 action
= self
.engine
.db
.get_one("alarms_action", {"uuid": alrm
.get("uuid")})
888 return self
._format
_out
(str(alarm_list
))
889 # to handle patch request for alarm update
890 elif cherrypy
.request
.method
== 'PATCH':
891 data
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
893 # check if uuid is valid
894 self
.engine
.db
.get_one("alarms", {"uuid": data
.get("uuid")})
896 return self
._format
_out
("Please provide valid alarm uuid.")
897 if data
.get("is_enable") is not None:
898 if data
.get("is_enable"):
901 alarm_status
= 'disabled'
902 self
.engine
.db
.set_one("alarms", {"uuid": data
.get("uuid")},
903 {"alarm_status": alarm_status
})
905 self
.engine
.db
.set_one("alarms", {"uuid": data
.get("uuid")},
906 {"threshold": data
.get("threshold")})
907 return self
._format
_out
("Alarm updated")
908 except Exception as e
:
909 cherrypy
.response
.status
= e
.http_code
.value
910 if isinstance(e
, (NbiException
, EngineException
, DbException
, FsException
, MsgException
, AuthException
,
911 ValidationError
, AuthconnException
)):
912 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
913 http_code_name
= e
.http_code
.name
914 cherrypy
.log("Exception {}".format(e
))
916 http_code_value
= cherrypy
.response
.status
= HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
917 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
918 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
920 "code": http_code_name
,
921 "status": http_code_value
,
924 return self
._format
_out
(problem_details
)
927 def token(self
, method
, token_id
=None, kwargs
=None):
929 # self.engine.load_dbase(cherrypy.request.app.config)
930 indata
= self
._format
_in
(kwargs
)
931 if not isinstance(indata
, dict):
933 "Expected application/yaml or application/json Content-Type",
934 HTTPStatus
.BAD_REQUEST
,
938 token_info
= self
.authenticator
.authorize()
940 self
._format
_login
(token_info
)
942 outdata
= self
.authenticator
.get_token(token_info
, token_id
)
944 outdata
= self
.authenticator
.get_token_list(token_info
)
945 elif method
== "POST":
947 token_info
= self
.authenticator
.authorize()
951 indata
.update(kwargs
)
952 # This is needed to log the user when authentication fails
953 cherrypy
.request
.login
= "{}".format(indata
.get("username", "-"))
954 outdata
= token_info
= self
.authenticator
.new_token(
955 token_info
, indata
, cherrypy
.request
.remote
957 cherrypy
.session
["Authorization"] = outdata
["_id"]
958 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
960 self
._format
_login
(token_info
)
961 # password expiry check
962 if self
.authenticator
.check_password_expiry(outdata
):
963 outdata
= {"id": outdata
["id"],
964 "message": "change_password",
965 "user_id": outdata
["user_id"]
967 # cherrypy.response.cookie["Authorization"] = outdata["id"]
968 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
969 elif method
== "DELETE":
970 if not token_id
and "id" in kwargs
:
971 token_id
= kwargs
["id"]
973 token_info
= self
.authenticator
.authorize()
975 self
._format
_login
(token_info
)
976 token_id
= token_info
["_id"]
977 outdata
= self
.authenticator
.del_token(token_id
)
979 cherrypy
.session
["Authorization"] = "logout"
980 # cherrypy.response.cookie["Authorization"] = token_id
981 # cherrypy.response.cookie["Authorization"]['expires'] = 0
984 "Method {} not allowed for token".format(method
),
985 HTTPStatus
.METHOD_NOT_ALLOWED
,
987 return self
._format
_out
(outdata
, token_info
)
990 def test(self
, *args
, **kwargs
):
991 if not cherrypy
.config
.get("server.enable_test") or (
992 isinstance(cherrypy
.config
["server.enable_test"], str)
993 and cherrypy
.config
["server.enable_test"].lower() == "false"
995 cherrypy
.response
.status
= HTTPStatus
.METHOD_NOT_ALLOWED
.value
996 return "test URL is disabled"
998 if args
and args
[0] == "help":
1000 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
1001 "sleep/<time>\nmessage/topic\n</pre></html>"
1004 elif args
and args
[0] == "init":
1006 # self.engine.load_dbase(cherrypy.request.app.config)
1007 self
.engine
.create_admin()
1008 return "Done. User 'admin', password 'admin' created"
1010 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1011 return self
._format
_out
("Database already initialized")
1012 elif args
and args
[0] == "file":
1013 return cherrypy
.lib
.static
.serve_file(
1014 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1],
1018 elif args
and args
[0] == "file2":
1020 cherrypy
.tree
.apps
["/osm"].config
["storage"]["path"] + "/" + args
[1]
1022 f
= open(f_path
, "r")
1023 cherrypy
.response
.headers
["Content-type"] = "text/plain"
1026 elif len(args
) == 2 and args
[0] == "db-clear":
1027 deleted_info
= self
.engine
.db
.del_list(args
[1], kwargs
)
1028 return "{} {} deleted\n".format(deleted_info
["deleted"], args
[1])
1029 elif len(args
) and args
[0] == "fs-clear":
1031 folders
= (args
[1],)
1033 folders
= self
.engine
.fs
.dir_ls(".")
1034 for folder
in folders
:
1035 self
.engine
.fs
.file_delete(folder
)
1036 return ",".join(folders
) + " folders deleted\n"
1037 elif args
and args
[0] == "login":
1038 if not cherrypy
.request
.headers
.get("Authorization"):
1039 cherrypy
.response
.headers
[
1041 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
1042 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1043 elif args
and args
[0] == "login2":
1044 if not cherrypy
.request
.headers
.get("Authorization"):
1045 cherrypy
.response
.headers
[
1047 ] = 'Bearer realm="Access to OSM site"'
1048 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
1049 elif args
and args
[0] == "sleep":
1052 sleep_time
= int(args
[1])
1054 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
1055 return self
._format
_out
("Database already initialized")
1056 thread_info
= cherrypy
.thread_data
1058 time
.sleep(sleep_time
)
1060 elif len(args
) >= 2 and args
[0] == "message":
1061 main_topic
= args
[1]
1062 return_text
= "<html><pre>{} ->\n".format(main_topic
)
1064 if cherrypy
.request
.method
== "POST":
1065 to_send
= yaml
.load(cherrypy
.request
.body
, Loader
=yaml
.SafeLoader
)
1066 for k
, v
in to_send
.items():
1067 self
.engine
.msg
.write(main_topic
, k
, v
)
1068 return_text
+= " {}: {}\n".format(k
, v
)
1069 elif cherrypy
.request
.method
== "GET":
1070 for k
, v
in kwargs
.items():
1071 v_dict
= yaml
.load(v
, Loader
=yaml
.SafeLoader
)
1072 self
.engine
.msg
.write(main_topic
, k
, v_dict
)
1073 return_text
+= " {}: {}\n".format(k
, v_dict
)
1074 except Exception as e
:
1075 return_text
+= "Error: " + str(e
)
1076 return_text
+= "</pre></html>\n"
1080 "<html><pre>\nheaders:\n args: {}\n".format(args
)
1081 + " kwargs: {}\n".format(kwargs
)
1082 + " headers: {}\n".format(cherrypy
.request
.headers
)
1083 + " path_info: {}\n".format(cherrypy
.request
.path_info
)
1084 + " query_string: {}\n".format(cherrypy
.request
.query_string
)
1085 + " session: {}\n".format(cherrypy
.session
)
1086 + " cookie: {}\n".format(cherrypy
.request
.cookie
)
1087 + " method: {}\n".format(cherrypy
.request
.method
)
1088 + " session: {}\n".format(cherrypy
.session
.get("fieldname"))
1091 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
1092 if cherrypy
.request
.body
.length
:
1093 return_text
+= " content: {}\n".format(
1095 cherrypy
.request
.body
.read(
1096 int(cherrypy
.request
.headers
.get("Content-Length", 0))
1101 return_text
+= "thread: {}\n".format(thread_info
)
1102 return_text
+= "</pre></html>"
1106 def _check_valid_url_method(method
, *args
):
1109 "URL must contain at least 'main_topic/version/topic'",
1110 HTTPStatus
.METHOD_NOT_ALLOWED
,
1113 reference
= valid_url_methods
1117 if not isinstance(reference
, dict):
1119 "URL contains unexpected extra items '{}'".format(arg
),
1120 HTTPStatus
.METHOD_NOT_ALLOWED
,
1123 if arg
in reference
:
1124 reference
= reference
[arg
]
1125 elif "<ID>" in reference
:
1126 reference
= reference
["<ID>"]
1127 elif "*" in reference
:
1128 # if there is content
1130 reference
= reference
["*"]
1134 "Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
1136 if "TODO" in reference
and method
in reference
["TODO"]:
1138 "Method {} not supported yet for this URL".format(method
),
1139 HTTPStatus
.NOT_IMPLEMENTED
,
1141 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
1143 "Method {} not supported for this URL".format(method
),
1144 HTTPStatus
.METHOD_NOT_ALLOWED
,
1146 return reference
["ROLE_PERMISSION"] + method
.lower()
1149 def _set_location_header(main_topic
, version
, topic
, id):
1151 Insert response header Location with the URL of created item base on URL params
1158 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
1159 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(
1160 main_topic
, version
, topic
, id
1165 def _extract_query_string_operations(kwargs
, method
):
1171 query_string_operations
= []
1173 for qs
in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1174 if qs
in kwargs
and kwargs
[qs
].lower() != "false":
1175 query_string_operations
.append(qs
.lower() + ":" + method
.lower())
1176 return query_string_operations
1179 def _manage_admin_query(token_info
, kwargs
, method
, _id
):
1181 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1182 Check that users has rights to use them and returs the admin_query
1183 :param token_info: token_info rights obtained by token
1184 :param kwargs: query string input.
1185 :param method: http method: GET, POSST, PUT, ...
1187 :return: admin_query dictionary with keys:
1188 public: True, False or None
1189 force: True or False
1190 project_id: tuple with projects used for accessing an element
1191 set_project: tuple with projects that a created element will belong to
1192 method: show, list, delete, write
1196 "project_id": (token_info
["project_id"],),
1197 "username": token_info
["username"],
1198 "admin": token_info
["admin"],
1200 "allow_show_user_project_role": token_info
["allow_show_user_project_role"],
1204 if "FORCE" in kwargs
:
1206 kwargs
["FORCE"].lower() != "false"
1207 ): # if None or True set force to True
1208 admin_query
["force"] = True
1211 if "PUBLIC" in kwargs
:
1213 kwargs
["PUBLIC"].lower() != "false"
1214 ): # if None or True set public to True
1215 admin_query
["public"] = True
1217 admin_query
["public"] = False
1218 del kwargs
["PUBLIC"]
1220 if "ADMIN" in kwargs
:
1221 behave_as
= kwargs
.pop("ADMIN")
1222 if behave_as
.lower() != "false":
1223 if not token_info
["admin"]:
1225 "Only admin projects can use 'ADMIN' query string",
1226 HTTPStatus
.UNAUTHORIZED
,
1229 not behave_as
or behave_as
.lower() == "true"
1230 ): # convert True, None to empty list
1231 admin_query
["project_id"] = ()
1232 elif isinstance(behave_as
, (list, tuple)):
1233 admin_query
["project_id"] = behave_as
1234 else: # isinstance(behave_as, str)
1235 admin_query
["project_id"] = (behave_as
,)
1236 if "SET_PROJECT" in kwargs
:
1237 set_project
= kwargs
.pop("SET_PROJECT")
1239 admin_query
["set_project"] = list(admin_query
["project_id"])
1241 if isinstance(set_project
, str):
1242 set_project
= (set_project
,)
1243 if admin_query
["project_id"]:
1244 for p
in set_project
:
1245 if p
not in admin_query
["project_id"]:
1247 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1248 "'ADMIN='{p}'".format(p
=p
),
1249 HTTPStatus
.UNAUTHORIZED
,
1251 admin_query
["set_project"] = set_project
1254 # if "PROJECT_READ" in kwargs:
1255 # admin_query["project"] = kwargs.pop("project")
1256 # if admin_query["project"] == token_info["project_id"]:
1259 admin_query
["method"] = "show"
1261 admin_query
["method"] = "list"
1262 elif method
== "DELETE":
1263 admin_query
["method"] = "delete"
1265 admin_query
["method"] = "write"
1285 engine_session
= None
1287 if not main_topic
or not version
or not topic
:
1289 "URL must contain at least 'main_topic/version/topic'",
1290 HTTPStatus
.METHOD_NOT_ALLOWED
,
1292 if main_topic
not in (
1303 "URL main_topic '{}' not supported".format(main_topic
),
1304 HTTPStatus
.METHOD_NOT_ALLOWED
,
1308 "URL version '{}' not supported".format(version
),
1309 HTTPStatus
.METHOD_NOT_ALLOWED
,
1314 and "METHOD" in kwargs
1315 and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1317 method
= kwargs
.pop("METHOD")
1319 method
= cherrypy
.request
.method
1321 role_permission
= self
._check
_valid
_url
_method
(
1322 method
, main_topic
, version
, topic
, _id
, item
, *args
1324 query_string_operations
= self
._extract
_query
_string
_operations
(
1327 if main_topic
== "admin" and topic
== "tokens":
1328 return self
.token(method
, _id
, kwargs
)
1329 token_info
= self
.authenticator
.authorize(
1330 role_permission
, query_string_operations
, _id
1332 if main_topic
== "admin" and topic
== "domains":
1333 return self
.domain()
1334 engine_session
= self
._manage
_admin
_query
(token_info
, kwargs
, method
, _id
)
1335 indata
= self
._format
_in
(kwargs
)
1336 engine_topic
= topic
1338 if item
and topic
!= "pm_jobs":
1341 if main_topic
== "nsd":
1342 engine_topic
= "nsds"
1343 elif main_topic
== "vnfpkgm":
1344 engine_topic
= "vnfds"
1345 if topic
== "vnfpkg_op_occs":
1346 engine_topic
= "vnfpkgops"
1347 if topic
== "vnf_packages" and item
== "action":
1348 engine_topic
= "vnfpkgops"
1349 elif main_topic
== "nslcm":
1350 engine_topic
= "nsrs"
1351 if topic
== "ns_lcm_op_occs":
1352 engine_topic
= "nslcmops"
1353 if topic
== "vnfrs" or topic
== "vnf_instances":
1354 engine_topic
= "vnfrs"
1355 elif main_topic
== "nst":
1356 engine_topic
= "nsts"
1357 elif main_topic
== "nsilcm":
1358 engine_topic
= "nsis"
1359 if topic
== "nsi_lcm_op_occs":
1360 engine_topic
= "nsilcmops"
1361 elif main_topic
== "pdu":
1362 engine_topic
= "pdus"
1364 engine_topic
== "vims"
1365 ): # TODO this is for backward compatibility, it will be removed in the future
1366 engine_topic
= "vim_accounts"
1368 if topic
== "subscriptions":
1369 engine_topic
= main_topic
+ "_" + topic
1381 if item
in ("vnfd", "nsd", "nst"):
1382 path
= "$DESCRIPTOR"
1385 elif item
== "artifacts":
1389 file, _format
= self
.engine
.get_file(
1394 cherrypy
.request
.headers
.get("Accept"),
1398 outdata
= self
.engine
.get_item_list(
1399 engine_session
, engine_topic
, kwargs
, api_req
=True
1402 if item
== "reports":
1403 # TODO check that project_id (_id in this context) has permissions
1406 if "vcaStatusRefresh" in kwargs
:
1407 filter_q
= {"vcaStatusRefresh": kwargs
["vcaStatusRefresh"]}
1408 outdata
= self
.engine
.get_item(engine_session
, engine_topic
, _id
, filter_q
, True)
1410 elif method
== "POST":
1411 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1413 "ns_descriptors_content",
1414 "vnf_packages_content",
1415 "netslice_templates_content",
1417 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
1419 _id
, _
= self
.engine
.new_item(
1425 cherrypy
.request
.headers
,
1427 completed
= self
.engine
.upload_content(
1433 cherrypy
.request
.headers
,
1436 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1438 cherrypy
.response
.headers
["Transaction-Id"] = _id
1439 outdata
= {"id": _id
}
1440 elif topic
== "ns_instances_content":
1442 _id
, _
= self
.engine
.new_item(
1443 rollback
, engine_session
, engine_topic
, indata
, kwargs
1446 indata
["lcmOperationType"] = "instantiate"
1447 indata
["nsInstanceId"] = _id
1448 nslcmop_id
, _
= self
.engine
.new_item(
1449 rollback
, engine_session
, "nslcmops", indata
, None
1451 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1452 outdata
= {"id": _id
, "nslcmop_id": nslcmop_id
}
1453 elif topic
== "ns_instances" and item
:
1454 indata
["lcmOperationType"] = item
1455 indata
["nsInstanceId"] = _id
1456 _id
, _
= self
.engine
.new_item(
1457 rollback
, engine_session
, "nslcmops", indata
, kwargs
1459 self
._set
_location
_header
(
1460 main_topic
, version
, "ns_lcm_op_occs", _id
1462 outdata
= {"id": _id
}
1463 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1464 elif topic
== "netslice_instances_content":
1465 # creates NetSlice_Instance_record (NSIR)
1466 _id
, _
= self
.engine
.new_item(
1467 rollback
, engine_session
, engine_topic
, indata
, kwargs
1469 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1470 indata
["lcmOperationType"] = "instantiate"
1471 indata
["netsliceInstanceId"] = _id
1472 nsilcmop_id
, _
= self
.engine
.new_item(
1473 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1475 outdata
= {"id": _id
, "nsilcmop_id": nsilcmop_id
}
1476 elif topic
== "netslice_instances" and item
:
1477 indata
["lcmOperationType"] = item
1478 indata
["netsliceInstanceId"] = _id
1479 _id
, _
= self
.engine
.new_item(
1480 rollback
, engine_session
, "nsilcmops", indata
, kwargs
1482 self
._set
_location
_header
(
1483 main_topic
, version
, "nsi_lcm_op_occs", _id
1485 outdata
= {"id": _id
}
1486 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1487 elif topic
== "vnf_packages" and item
== "action":
1488 indata
["lcmOperationType"] = item
1489 indata
["vnfPkgId"] = _id
1490 _id
, _
= self
.engine
.new_item(
1491 rollback
, engine_session
, "vnfpkgops", indata
, kwargs
1493 self
._set
_location
_header
(
1494 main_topic
, version
, "vnfpkg_op_occs", _id
1496 outdata
= {"id": _id
}
1497 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1498 elif topic
== "subscriptions":
1499 _id
, _
= self
.engine
.new_item(
1500 rollback
, engine_session
, engine_topic
, indata
, kwargs
1502 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1504 link
["self"] = cherrypy
.response
.headers
["Location"]
1507 "filter": indata
["filter"],
1508 "callbackUri": indata
["CallbackUri"],
1511 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
1513 _id
, op_id
= self
.engine
.new_item(
1519 cherrypy
.request
.headers
,
1521 self
._set
_location
_header
(main_topic
, version
, topic
, _id
)
1522 outdata
= {"id": _id
}
1524 outdata
["op_id"] = op_id
1525 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1526 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
1528 elif method
== "DELETE":
1530 outdata
= self
.engine
.del_item_list(
1531 engine_session
, engine_topic
, kwargs
1533 cherrypy
.response
.status
= HTTPStatus
.OK
.value
1534 else: # len(args) > 1
1535 # for NS NSI generate an operation
1537 if topic
== "ns_instances_content" and not engine_session
["force"]:
1539 "lcmOperationType": "terminate",
1540 "nsInstanceId": _id
,
1543 op_id
, _
= self
.engine
.new_item(
1544 rollback
, engine_session
, "nslcmops", nslcmop_desc
, kwargs
1547 outdata
= {"_id": op_id
}
1549 topic
== "netslice_instances_content"
1550 and not engine_session
["force"]
1553 "lcmOperationType": "terminate",
1554 "netsliceInstanceId": _id
,
1557 op_id
, _
= self
.engine
.new_item(
1558 rollback
, engine_session
, "nsilcmops", nsilcmop_desc
, None
1561 outdata
= {"_id": op_id
}
1562 # if there is not any deletion in process, delete
1564 op_id
= self
.engine
.del_item(engine_session
, engine_topic
, _id
)
1566 outdata
= {"op_id": op_id
}
1567 cherrypy
.response
.status
= (
1568 HTTPStatus
.ACCEPTED
.value
1570 else HTTPStatus
.NO_CONTENT
.value
1573 elif method
in ("PUT", "PATCH"):
1575 if not indata
and not kwargs
and not engine_session
.get("set_project"):
1577 "Nothing to update. Provide payload and/or query string",
1578 HTTPStatus
.BAD_REQUEST
,
1581 item
in ("nsd_content", "package_content", "nst_content")
1584 completed
= self
.engine
.upload_content(
1590 cherrypy
.request
.headers
,
1593 cherrypy
.response
.headers
["Transaction-Id"] = id
1595 op_id
= self
.engine
.edit_item(
1596 engine_session
, engine_topic
, _id
, indata
, kwargs
1600 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
1601 outdata
= {"op_id": op_id
}
1603 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
1607 "Method {} not allowed".format(method
),
1608 HTTPStatus
.METHOD_NOT_ALLOWED
,
1611 # if Role information changes, it is needed to reload the information of roles
1612 if topic
== "roles" and method
!= "GET":
1613 self
.authenticator
.load_operation_to_allowed_roles()
1617 and method
== "DELETE"
1618 or topic
in ["users", "roles"]
1619 and method
in ["PUT", "PATCH", "DELETE"]
1621 self
.authenticator
.remove_token_from_cache()
1623 return self
._format
_out
(outdata
, token_info
, _format
)
1624 except Exception as e
:
1638 http_code_value
= cherrypy
.response
.status
= e
.http_code
.value
1639 http_code_name
= e
.http_code
.name
1640 cherrypy
.log("Exception {}".format(e
))
1643 cherrypy
.response
.status
1644 ) = HTTPStatus
.BAD_REQUEST
.value
# INTERNAL_SERVER_ERROR
1645 cherrypy
.log("CRITICAL: Exception {}".format(e
), traceback
=True)
1646 http_code_name
= HTTPStatus
.BAD_REQUEST
.name
1647 if hasattr(outdata
, "close"): # is an open file
1651 for rollback_item
in rollback
:
1653 if rollback_item
.get("operation") == "set":
1654 self
.engine
.db
.set_one(
1655 rollback_item
["topic"],
1656 {"_id": rollback_item
["_id"]},
1657 rollback_item
["content"],
1658 fail_on_empty
=False,
1660 elif rollback_item
.get("operation") == "del_list":
1661 self
.engine
.db
.del_list(
1662 rollback_item
["topic"],
1663 rollback_item
["filter"],
1664 fail_on_empty
=False,
1667 self
.engine
.db
.del_one(
1668 rollback_item
["topic"],
1669 {"_id": rollback_item
["_id"]},
1670 fail_on_empty
=False,
1672 except Exception as e2
:
1673 rollback_error_text
= "Rollback Exception {}: {}".format(
1676 cherrypy
.log(rollback_error_text
)
1677 error_text
+= ". " + rollback_error_text
1678 # if isinstance(e, MsgException):
1679 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1680 # engine_topic[:-1], method, error_text)
1682 "code": http_code_name
,
1683 "status": http_code_value
,
1684 "detail": error_text
,
1686 return self
._format
_out
(problem_details
, token_info
)
1687 # raise cherrypy.HTTPError(e.http_code.value, str(e))
1690 self
._format
_login
(token_info
)
1691 if method
in ("PUT", "PATCH", "POST") and isinstance(outdata
, dict):
1692 for logging_id
in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1693 if outdata
.get(logging_id
):
1694 cherrypy
.request
.login
+= ";{}={}".format(
1695 logging_id
, outdata
[logging_id
][:36]
1699 def _start_service():
1701 Callback function called when cherrypy.engine starts
1702 Override configuration with env variables
1703 Set database, storage, message configuration
1704 Init database with admin/admin user password
1707 global subscription_thread
1708 cherrypy
.log
.error("Starting osm_nbi")
1709 # update general cherrypy configuration
1712 engine_config
= cherrypy
.tree
.apps
["/osm"].config
1713 for k
, v
in environ
.items():
1714 if not k
.startswith("OSMNBI_"):
1716 k1
, _
, k2
= k
[7:].lower().partition("_")
1720 # update static configuration
1721 if k
== "OSMNBI_STATIC_DIR":
1722 engine_config
["/static"]["tools.staticdir.dir"] = v
1723 engine_config
["/static"]["tools.staticdir.on"] = True
1724 elif k
== "OSMNBI_SOCKET_PORT" or k
== "OSMNBI_SERVER_PORT":
1725 update_dict
["server.socket_port"] = int(v
)
1726 elif k
== "OSMNBI_SOCKET_HOST" or k
== "OSMNBI_SERVER_HOST":
1727 update_dict
["server.socket_host"] = v
1728 elif k1
in ("server", "test", "auth", "log"):
1729 update_dict
[k1
+ "." + k2
] = v
1730 elif k1
in ("message", "database", "storage", "authentication"):
1731 # k2 = k2.replace('_', '.')
1732 if k2
in ("port", "db_port"):
1733 engine_config
[k1
][k2
] = int(v
)
1735 engine_config
[k1
][k2
] = v
1737 except ValueError as e
:
1738 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
1739 except Exception as e
:
1740 cherrypy
.log
.warn("skipping environ '{}' on exception '{}'".format(k
, e
))
1743 cherrypy
.config
.update(update_dict
)
1744 engine_config
["global"].update(update_dict
)
1747 log_format_simple
= (
1748 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1750 log_formatter_simple
= logging
.Formatter(
1751 log_format_simple
, datefmt
="%Y-%m-%dT%H:%M:%S"
1753 logger_server
= logging
.getLogger("cherrypy.error")
1754 logger_access
= logging
.getLogger("cherrypy.access")
1755 logger_cherry
= logging
.getLogger("cherrypy")
1756 logger_nbi
= logging
.getLogger("nbi")
1758 if "log.file" in engine_config
["global"]:
1759 file_handler
= logging
.handlers
.RotatingFileHandler(
1760 engine_config
["global"]["log.file"], maxBytes
=100e6
, backupCount
=9, delay
=0
1762 file_handler
.setFormatter(log_formatter_simple
)
1763 logger_cherry
.addHandler(file_handler
)
1764 logger_nbi
.addHandler(file_handler
)
1765 # log always to standard output
1766 for format_
, logger
in {
1767 "nbi.server %(filename)s:%(lineno)s": logger_server
,
1768 "nbi.access %(filename)s:%(lineno)s": logger_access
,
1769 "%(name)s %(filename)s:%(lineno)s": logger_nbi
,
1771 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
1772 log_formatter_cherry
= logging
.Formatter(
1773 log_format_cherry
, datefmt
="%Y-%m-%dT%H:%M:%S"
1775 str_handler
= logging
.StreamHandler()
1776 str_handler
.setFormatter(log_formatter_cherry
)
1777 logger
.addHandler(str_handler
)
1779 if engine_config
["global"].get("log.level"):
1780 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
1781 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
1783 # logging other modules
1784 for k1
, logname
in {
1785 "message": "nbi.msg",
1786 "database": "nbi.db",
1787 "storage": "nbi.fs",
1789 engine_config
[k1
]["logger_name"] = logname
1790 logger_module
= logging
.getLogger(logname
)
1791 if "logfile" in engine_config
[k1
]:
1792 file_handler
= logging
.handlers
.RotatingFileHandler(
1793 engine_config
[k1
]["logfile"], maxBytes
=100e6
, backupCount
=9, delay
=0
1795 file_handler
.setFormatter(log_formatter_simple
)
1796 logger_module
.addHandler(file_handler
)
1797 if "loglevel" in engine_config
[k1
]:
1798 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
1799 # TODO add more entries, e.g.: storage
1800 cherrypy
.tree
.apps
["/osm"].root
.engine
.start(engine_config
)
1801 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.start(engine_config
)
1802 cherrypy
.tree
.apps
["/osm"].root
.engine
.init_db(target_version
=database_version
)
1803 cherrypy
.tree
.apps
["/osm"].root
.authenticator
.init_db(
1804 target_version
=auth_database_version
1807 # start subscriptions thread:
1808 subscription_thread
= SubscriptionThread(
1809 config
=engine_config
, engine
=nbi_server
.engine
1811 subscription_thread
.start()
1812 # Do not capture except SubscriptionException
1814 backend
= engine_config
["authentication"]["backend"]
1816 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1817 nbi_version
, nbi_version_date
, backend
1822 def _stop_service():
1824 Callback function called when cherrypy.engine stops
1825 TODO: Ending database connections.
1827 global subscription_thread
1828 if subscription_thread
:
1829 subscription_thread
.terminate()
1830 subscription_thread
= None
1831 cherrypy
.tree
.apps
["/osm"].root
.engine
.stop()
1832 cherrypy
.log
.error("Stopping osm_nbi")
1835 def nbi(config_file
):
1839 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1840 # 'tools.sessions.on': True,
1841 # 'tools.response_headers.on': True,
1842 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1845 # cherrypy.Server.ssl_module = 'builtin'
1846 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1847 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1848 # cherrypy.Server.thread_pool = 10
1849 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1851 # cherrypy.config.update({'tools.auth_basic.on': True,
1852 # 'tools.auth_basic.realm': 'localhost',
1853 # 'tools.auth_basic.checkpassword': validate_password})
1854 nbi_server
= Server()
1855 cherrypy
.engine
.subscribe("start", _start_service
)
1856 cherrypy
.engine
.subscribe("stop", _stop_service
)
1857 cherrypy
.quickstart(nbi_server
, "/osm", config_file
)
1862 """Usage: {} [options]
1863 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1864 -h|--help: shows this help
1869 # --log-socket-host HOST: send logs to this host")
1870 # --log-socket-port PORT: send logs using this port (default: 9022)")
1873 if __name__
== "__main__":
1875 # load parameters and configuration
1876 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
1877 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1880 if o
in ("-h", "--help"):
1883 elif o
in ("-c", "--config"):
1885 # elif o == "--log-socket-port":
1886 # log_socket_port = a
1887 # elif o == "--log-socket-host":
1888 # log_socket_host = a
1889 # elif o == "--log-file":
1892 assert False, "Unhandled option"
1894 if not path
.isfile(config_file
):
1896 "configuration file '{}' that not exist".format(config_file
),
1901 for config_file
in (
1902 __file__
[: __file__
.rfind(".")] + ".cfg",
1906 if path
.isfile(config_file
):
1910 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
1915 except getopt
.GetoptError
as e
:
1916 print(str(e
), file=sys
.stderr
)