0a4c4027ef1165a81bbeb0f89549c915c3c102c9
2 # -*- coding: utf-8 -*-
8 import html_out
as html
10 import logging
.handlers
14 from authconn
import AuthException
15 from auth
import Authenticator
16 from engine
import Engine
, EngineException
17 from osm_common
.dbbase
import DbException
18 from osm_common
.fsbase
import FsException
19 from osm_common
.msgbase
import MsgException
20 from http
import HTTPStatus
21 from codecs
import getreader
22 from os
import environ
, path
24 __author__
= "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
26 # TODO consider to remove and provide version using the static version file
28 version_date
= "Apr 2018"
29 database_version
= '1.0'
30 auth_database_version
= '1.0'
33 North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
34 URL: /osm GET POST PUT DELETE PATCH
36 /ns_descriptors_content O O
42 /artifacts[/<artifactPath>] O
50 /vnf_packages_content O O
54 /package_content O5 O5
57 /artifacts[/<artifactPath>] O5
62 /ns_instances_content O O
74 /vnf_instances (also vnfrs for compatibility) O
88 /vims_accounts (also vims for compatibility) O O
94 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
95 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
96 item of the array, that is, pass if any item of the array pass the filter.
97 It allows both ne and neq for not equal
98 TODO: 4.3.3 Attribute selectors
99 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
100 (none) … same as “exclude_default”
101 all_fields … all attributes.
102 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
103 conditionally mandatory, and that are not provided in <list>.
104 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
105 are not conditionally mandatory, and that are provided in <list>.
106 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
107 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
108 the particular resource
109 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
110 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
111 present specification for the particular resource, but that are not part of <list>
112 Header field name Reference Example Descriptions
113 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
114 This header field shall be present if the response is expected to have a non-empty message body.
115 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
116 This header field shall be present if the request has a non-empty message body.
117 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
118 Details are specified in clause 4.5.3.
119 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
120 Header field name Reference Example Descriptions
121 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
122 This header field shall be present if the response has a non-empty message body.
123 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
124 new resource has been created.
125 This header field shall be present if the response status code is 201 or 3xx.
126 In the present document this header field is also used if the response status code is 202 and a new resource was
128 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
129 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
131 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
133 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
134 response, and the total length of the file.
135 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
139 class NbiException(Exception):
141 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
142 Exception.__init
__(self
, message
)
143 self
.http_code
= http_code
146 class Server(object):
148 # to decode bytes to str
149 reader
= getreader("utf-8")
153 self
.engine
= Engine()
154 self
.authenticator
= Authenticator()
155 self
.valid_methods
= { # contains allowed URL and methods
158 "tokens": {"METHODS": ("GET", "POST", "DELETE"),
159 "<ID>": {"METHODS": ("GET", "DELETE")}
161 "users": {"METHODS": ("GET", "POST"),
162 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT")}
164 "projects": {"METHODS": ("GET", "POST"),
165 "<ID>": {"METHODS": ("GET", "DELETE")}
167 "vims": {"METHODS": ("GET", "POST"),
168 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
170 "vim_accounts": {"METHODS": ("GET", "POST"),
171 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
173 "sdns": {"METHODS": ("GET", "POST"),
174 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT")}
180 "pdu_descriptors": {"METHODS": ("GET", "POST"),
181 "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT")}
187 "ns_descriptors_content": {"METHODS": ("GET", "POST"),
188 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
190 "ns_descriptors": {"METHODS": ("GET", "POST"),
191 "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH",
192 "nsd_content": {"METHODS": ("GET", "PUT")},
193 "nsd": {"METHODS": "GET"}, # descriptor inside package
194 "artifacts": {"*": {"METHODS": "GET"}}
197 "pnf_descriptors": {"TODO": ("GET", "POST"),
198 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
199 "pnfd_content": {"TODO": ("GET", "PUT")}
202 "subscriptions": {"TODO": ("GET", "POST"),
203 "<ID>": {"TODO": ("GET", "DELETE")}
209 "vnf_packages_content": {"METHODS": ("GET", "POST"),
210 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
212 "vnf_packages": {"METHODS": ("GET", "POST"),
213 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
214 "package_content": {"METHODS": ("GET", "PUT"), # package
215 "upload_from_uri": {"TODO": "POST"}
217 "vnfd": {"METHODS": "GET"}, # descriptor inside package
218 "artifacts": {"*": {"METHODS": "GET"}}
221 "subscriptions": {"TODO": ("GET", "POST"),
222 "<ID>": {"TODO": ("GET", "DELETE")}
228 "ns_instances_content": {"METHODS": ("GET", "POST"),
229 "<ID>": {"METHODS": ("GET", "DELETE")}
231 "ns_instances": {"METHODS": ("GET", "POST"),
232 "<ID>": {"METHODS": ("GET", "DELETE"),
233 "scale": {"METHODS": "POST"},
234 "terminate": {"METHODS": "POST"},
235 "instantiate": {"METHODS": "POST"},
236 "action": {"METHODS": "POST"},
239 "ns_lcm_op_occs": {"METHODS": "GET",
240 "<ID>": {"METHODS": "GET"},
242 "vnfrs": {"METHODS": ("GET"),
243 "<ID>": {"METHODS": ("GET")}
245 "vnf_instances": {"METHODS": ("GET"),
246 "<ID>": {"METHODS": ("GET")}
252 def _format_in(self
, kwargs
):
255 if cherrypy
.request
.body
.length
:
256 error_text
= "Invalid input format "
258 if "Content-Type" in cherrypy
.request
.headers
:
259 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
260 error_text
= "Invalid json format "
261 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
262 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
263 error_text
= "Invalid yaml format "
264 indata
= yaml
.load(cherrypy
.request
.body
)
265 elif "application/binary" in cherrypy
.request
.headers
["Content-Type"] or \
266 "application/gzip" in cherrypy
.request
.headers
["Content-Type"] or \
267 "application/zip" in cherrypy
.request
.headers
["Content-Type"] or \
268 "text/plain" in cherrypy
.request
.headers
["Content-Type"]:
269 indata
= cherrypy
.request
.body
# .read()
270 elif "multipart/form-data" in cherrypy
.request
.headers
["Content-Type"]:
271 if "descriptor_file" in kwargs
:
272 filecontent
= kwargs
.pop("descriptor_file")
273 if not filecontent
.file:
274 raise NbiException("empty file or content", HTTPStatus
.BAD_REQUEST
)
275 indata
= filecontent
.file # .read()
276 if filecontent
.content_type
.value
:
277 cherrypy
.request
.headers
["Content-Type"] = filecontent
.content_type
.value
279 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
280 # "Only 'Content-Type' of type 'application/json' or
281 # 'application/yaml' for input format are available")
282 error_text
= "Invalid yaml format "
283 indata
= yaml
.load(cherrypy
.request
.body
)
285 error_text
= "Invalid yaml format "
286 indata
= yaml
.load(cherrypy
.request
.body
)
291 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
294 for k
, v
in kwargs
.items():
295 if isinstance(v
, str):
300 kwargs
[k
] = yaml
.load(v
)
303 elif k
.endswith(".gt") or k
.endswith(".lt") or k
.endswith(".gte") or k
.endswith(".lte"):
311 elif v
.find(",") > 0:
312 kwargs
[k
] = v
.split(",")
313 elif isinstance(v
, (list, tuple)):
314 for index
in range(0, len(v
)):
319 v
[index
] = yaml
.load(v
[index
])
324 except (ValueError, yaml
.YAMLError
) as exc
:
325 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
326 except KeyError as exc
:
327 raise NbiException("Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
)
328 except Exception as exc
:
329 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
332 def _format_out(data
, session
=None, _format
=None):
334 return string of dictionary data according to requested json, yaml, xml. By default json
335 :param data: response to be sent. Can be a dict, text or file
337 :param _format: The format to be set as Content-Type ir data is a file
340 accept
= cherrypy
.request
.headers
.get("Accept")
342 if accept
and "text/html" in accept
:
343 return html
.format(data
, cherrypy
.request
, cherrypy
.response
, session
)
344 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
346 elif hasattr(data
, "read"): # file object
348 cherrypy
.response
.headers
["Content-Type"] = _format
349 elif "b" in data
.mode
: # binariy asssumig zip
350 cherrypy
.response
.headers
["Content-Type"] = 'application/zip'
352 cherrypy
.response
.headers
["Content-Type"] = 'text/plain'
353 # TODO check that cherrypy close file. If not implement pending things to close per thread next
356 if "application/json" in accept
:
357 cherrypy
.response
.headers
["Content-Type"] = 'application/json; charset=utf-8'
358 a
= json
.dumps(data
, indent
=4) + "\n"
359 return a
.encode("utf8")
360 elif "text/html" in accept
:
361 return html
.format(data
, cherrypy
.request
, cherrypy
.response
, session
)
363 elif "application/yaml" in accept
or "*/*" in accept
or "text/plain" in accept
:
366 raise cherrypy
.HTTPError(HTTPStatus
.NOT_ACCEPTABLE
.value
,
367 "Only 'Accept' of type 'application/json' or 'application/yaml' "
368 "for output format are available")
369 cherrypy
.response
.headers
["Content-Type"] = 'application/yaml'
370 return yaml
.safe_dump(data
, explicit_start
=True, indent
=4, default_flow_style
=False, tags
=False,
371 encoding
='utf-8', allow_unicode
=True) # , canonical=True, default_style='"'
374 def index(self
, *args
, **kwargs
):
377 if cherrypy
.request
.method
== "GET":
378 session
= self
.authenticator
.authorize()
379 outdata
= "Index page"
381 raise cherrypy
.HTTPError(HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
382 "Method {} not allowed for tokens".format(cherrypy
.request
.method
))
384 return self
._format
_out
(outdata
, session
)
386 except (EngineException
, AuthException
) as e
:
387 cherrypy
.log("index Exception {}".format(e
))
388 cherrypy
.response
.status
= e
.http_code
.value
389 return self
._format
_out
("Welcome to OSM!", session
)
392 def version(self
, *args
, **kwargs
):
393 # TODO consider to remove and provide version using the static version file
394 global __version__
, version_date
396 if cherrypy
.request
.method
!= "GET":
397 raise NbiException("Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
)
399 raise NbiException("Invalid URL or query string for version", HTTPStatus
.METHOD_NOT_ALLOWED
)
400 return __version__
+ " " + version_date
401 except NbiException
as e
:
402 cherrypy
.response
.status
= e
.http_code
.value
404 "code": e
.http_code
.name
,
405 "status": e
.http_code
.value
,
408 return self
._format
_out
(problem_details
, None)
411 def token(self
, method
, token_id
=None, kwargs
=None):
413 # self.engine.load_dbase(cherrypy.request.app.config)
414 indata
= self
._format
_in
(kwargs
)
415 if not isinstance(indata
, dict):
416 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus
.BAD_REQUEST
)
419 session
= self
.authenticator
.authorize()
421 outdata
= self
.authenticator
.get_token(session
, token_id
)
423 outdata
= self
.authenticator
.get_token_list(session
)
424 elif method
== "POST":
426 session
= self
.authenticator
.authorize()
430 indata
.update(kwargs
)
431 outdata
= self
.authenticator
.new_token(session
, indata
, cherrypy
.request
.remote
)
433 cherrypy
.session
['Authorization'] = outdata
["_id"]
434 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
435 # cherrypy.response.cookie["Authorization"] = outdata["id"]
436 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
437 elif method
== "DELETE":
438 if not token_id
and "id" in kwargs
:
439 token_id
= kwargs
["id"]
441 session
= self
.authenticator
.authorize()
442 token_id
= session
["_id"]
443 outdata
= self
.authenticator
.del_token(token_id
)
445 cherrypy
.session
['Authorization'] = "logout"
446 # cherrypy.response.cookie["Authorization"] = token_id
447 # cherrypy.response.cookie["Authorization"]['expires'] = 0
449 raise NbiException("Method {} not allowed for token".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
450 return self
._format
_out
(outdata
, session
)
451 except (NbiException
, EngineException
, DbException
, AuthException
) as e
:
452 cherrypy
.log("tokens Exception {}".format(e
))
453 cherrypy
.response
.status
= e
.http_code
.value
455 "code": e
.http_code
.name
,
456 "status": e
.http_code
.value
,
459 return self
._format
_out
(problem_details
, session
)
462 def test(self
, *args
, **kwargs
):
464 if args
and args
[0] == "help":
465 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nprune\nlogin\nlogin2\n"\
466 "sleep/<time>\nmessage/topic\n</pre></html>"
468 elif args
and args
[0] == "init":
470 # self.engine.load_dbase(cherrypy.request.app.config)
471 self
.engine
.create_admin()
472 return "Done. User 'admin', password 'admin' created"
474 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
475 return self
._format
_out
("Database already initialized")
476 elif args
and args
[0] == "file":
477 return cherrypy
.lib
.static
.serve_file(cherrypy
.tree
.apps
['/osm'].config
["storage"]["path"] + "/" + args
[1],
478 "text/plain", "attachment")
479 elif args
and args
[0] == "file2":
480 f_path
= cherrypy
.tree
.apps
['/osm'].config
["storage"]["path"] + "/" + args
[1]
481 f
= open(f_path
, "r")
482 cherrypy
.response
.headers
["Content-type"] = "text/plain"
485 elif len(args
) == 2 and args
[0] == "db-clear":
486 return self
.engine
.del_item_list({"project_id": "admin", "admin": True}, args
[1], kwargs
)
487 elif args
and args
[0] == "prune":
488 return self
.engine
.prune()
489 elif args
and args
[0] == "login":
490 if not cherrypy
.request
.headers
.get("Authorization"):
491 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
492 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
493 elif args
and args
[0] == "login2":
494 if not cherrypy
.request
.headers
.get("Authorization"):
495 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
496 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
497 elif args
and args
[0] == "sleep":
500 sleep_time
= int(args
[1])
502 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
503 return self
._format
_out
("Database already initialized")
504 thread_info
= cherrypy
.thread_data
506 time
.sleep(sleep_time
)
508 elif len(args
) >= 2 and args
[0] == "message":
510 return_text
= "<html><pre>{} ->\n".format(topic
)
512 if cherrypy
.request
.method
== 'POST':
513 to_send
= yaml
.load(cherrypy
.request
.body
)
514 for k
, v
in to_send
.items():
515 self
.engine
.msg
.write(topic
, k
, v
)
516 return_text
+= " {}: {}\n".format(k
, v
)
517 elif cherrypy
.request
.method
== 'GET':
518 for k
, v
in kwargs
.items():
519 self
.engine
.msg
.write(topic
, k
, yaml
.load(v
))
520 return_text
+= " {}: {}\n".format(k
, yaml
.load(v
))
521 except Exception as e
:
522 return_text
+= "Error: " + str(e
)
523 return_text
+= "</pre></html>\n"
527 "<html><pre>\nheaders:\n args: {}\n".format(args
) +
528 " kwargs: {}\n".format(kwargs
) +
529 " headers: {}\n".format(cherrypy
.request
.headers
) +
530 " path_info: {}\n".format(cherrypy
.request
.path_info
) +
531 " query_string: {}\n".format(cherrypy
.request
.query_string
) +
532 " session: {}\n".format(cherrypy
.session
) +
533 " cookie: {}\n".format(cherrypy
.request
.cookie
) +
534 " method: {}\n".format(cherrypy
.request
.method
) +
535 " session: {}\n".format(cherrypy
.session
.get('fieldname')) +
537 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
538 if cherrypy
.request
.body
.length
:
539 return_text
+= " content: {}\n".format(
540 str(cherrypy
.request
.body
.read(int(cherrypy
.request
.headers
.get('Content-Length', 0)))))
542 return_text
+= "thread: {}\n".format(thread_info
)
543 return_text
+= "</pre></html>"
546 def _check_valid_url_method(self
, method
, *args
):
548 raise NbiException("URL must contain at least 'topic/version/item'", HTTPStatus
.METHOD_NOT_ALLOWED
)
550 reference
= self
.valid_methods
554 if not isinstance(reference
, dict):
555 raise NbiException("URL contains unexpected extra items '{}'".format(arg
),
556 HTTPStatus
.METHOD_NOT_ALLOWED
)
559 reference
= reference
[arg
]
560 elif "<ID>" in reference
:
561 reference
= reference
["<ID>"]
562 elif "*" in reference
:
563 reference
= reference
["*"]
566 raise NbiException("Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
)
567 if "TODO" in reference
and method
in reference
["TODO"]:
568 raise NbiException("Method {} not supported yet for this URL".format(method
), HTTPStatus
.NOT_IMPLEMENTED
)
569 elif "METHODS" in reference
and method
not in reference
["METHODS"]:
570 raise NbiException("Method {} not supported for this URL".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
574 def _set_location_header(topic
, version
, item
, id):
576 Insert response header Location with the URL of created item base on URL params
583 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
584 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(topic
, version
, item
, id)
588 def default(self
, topic
=None, version
=None, item
=None, _id
=None, item2
=None, *args
, **kwargs
):
597 if not topic
or not version
or not item
:
598 raise NbiException("URL must contain at least 'topic/version/item'", HTTPStatus
.METHOD_NOT_ALLOWED
)
599 if topic
not in ("admin", "vnfpkgm", "nsd", "nslcm"):
600 raise NbiException("URL topic '{}' not supported".format(topic
), HTTPStatus
.METHOD_NOT_ALLOWED
)
602 raise NbiException("URL version '{}' not supported".format(version
), HTTPStatus
.METHOD_NOT_ALLOWED
)
604 if kwargs
and "METHOD" in kwargs
and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
605 method
= kwargs
.pop("METHOD")
607 method
= cherrypy
.request
.method
608 if kwargs
and "FORCE" in kwargs
:
609 force
= kwargs
.pop("FORCE")
613 self
._check
_valid
_url
_method
(method
, topic
, version
, item
, _id
, item2
, *args
)
615 if topic
== "admin" and item
== "tokens":
616 return self
.token(method
, _id
, kwargs
)
618 # self.engine.load_dbase(cherrypy.request.app.config)
619 session
= self
.authenticator
.authorize()
620 indata
= self
._format
_in
(kwargs
)
622 if item
== "subscriptions":
623 engine_item
= topic
+ "_" + item
629 elif topic
== "vnfpkgm":
630 engine_item
= "vnfds"
631 elif topic
== "nslcm":
633 if item
== "ns_lcm_op_occs":
634 engine_item
= "nslcmops"
635 if item
== "vnfrs" or item
== "vnf_instances":
636 engine_item
= "vnfrs"
639 if engine_item
== "vims": # TODO this is for backward compatibility, it will remove in the future
640 engine_item
= "vim_accounts"
643 if item2
in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd"):
644 if item2
in ("vnfd", "nsd"):
648 elif item2
== "artifacts":
652 file, _format
= self
.engine
.get_file(session
, engine_item
, _id
, path
,
653 cherrypy
.request
.headers
.get("Accept"))
656 outdata
= self
.engine
.get_item_list(session
, engine_item
, kwargs
)
658 outdata
= self
.engine
.get_item(session
, engine_item
, _id
)
659 elif method
== "POST":
660 if item
in ("ns_descriptors_content", "vnf_packages_content"):
661 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
663 _id
= self
.engine
.new_item(rollback
, session
, engine_item
, {}, None, cherrypy
.request
.headers
,
665 completed
= self
.engine
.upload_content(session
, engine_item
, _id
, indata
, kwargs
,
666 cherrypy
.request
.headers
)
668 self
._set
_location
_header
(topic
, version
, item
, _id
)
670 cherrypy
.response
.headers
["Transaction-Id"] = _id
671 outdata
= {"id": _id
}
672 elif item
== "ns_instances_content":
673 _id
= self
.engine
.new_item(rollback
, session
, engine_item
, indata
, kwargs
, force
=force
)
674 self
.engine
.ns_operation(rollback
, session
, _id
, "instantiate", indata
, None)
675 self
._set
_location
_header
(topic
, version
, item
, _id
)
676 outdata
= {"id": _id
}
677 elif item
== "ns_instances" and item2
:
678 _id
= self
.engine
.ns_operation(rollback
, session
, _id
, item2
, indata
, kwargs
)
679 self
._set
_location
_header
(topic
, version
, "ns_lcm_op_occs", _id
)
680 outdata
= {"id": _id
}
681 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
683 _id
= self
.engine
.new_item(rollback
, session
, engine_item
, indata
, kwargs
, cherrypy
.request
.headers
,
685 self
._set
_location
_header
(topic
, version
, item
, _id
)
686 outdata
= {"id": _id
}
687 # TODO form NsdInfo when item in ("ns_descriptors", "vnf_packages")
688 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
690 elif method
== "DELETE":
692 outdata
= self
.engine
.del_item_list(session
, engine_item
, kwargs
)
693 cherrypy
.response
.status
= HTTPStatus
.OK
.value
694 else: # len(args) > 1
695 if item
== "ns_instances_content" and not force
:
696 opp_id
= self
.engine
.ns_operation(rollback
, session
, _id
, "terminate", {"autoremove": True},
698 outdata
= {"_id": opp_id
}
699 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
701 self
.engine
.del_item(session
, engine_item
, _id
, force
)
702 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
703 if engine_item
in ("vim_accounts", "sdns"):
704 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
706 elif method
in ("PUT", "PATCH"):
708 if not indata
and not kwargs
:
709 raise NbiException("Nothing to update. Provide payload and/or query string",
710 HTTPStatus
.BAD_REQUEST
)
711 if item2
in ("nsd_content", "package_content") and method
== "PUT":
712 completed
= self
.engine
.upload_content(session
, engine_item
, _id
, indata
, kwargs
,
713 cherrypy
.request
.headers
)
715 cherrypy
.response
.headers
["Transaction-Id"] = id
717 self
.engine
.edit_item(session
, engine_item
, _id
, indata
, kwargs
, force
=force
)
718 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
720 raise NbiException("Method {} not allowed".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
721 return self
._format
_out
(outdata
, session
, _format
)
722 except (NbiException
, EngineException
, DbException
, FsException
, MsgException
, AuthException
) as e
:
723 cherrypy
.log("Exception {}".format(e
))
724 cherrypy
.response
.status
= e
.http_code
.value
725 if hasattr(outdata
, "close"): # is an open file
727 for rollback_item
in rollback
:
729 self
.engine
.del_item(**rollback_item
, session
=session
, force
=True)
730 except Exception as e2
:
731 cherrypy
.log("Rollback Exception {}: {}".format(rollback_item
, e2
))
733 if isinstance(e
, MsgException
):
734 error_text
= "{} has been '{}' but other modules cannot be informed because an error on bus".format(
735 engine_item
[:-1], method
, error_text
)
737 "code": e
.http_code
.name
,
738 "status": e
.http_code
.value
,
741 return self
._format
_out
(problem_details
, session
)
742 # raise cherrypy.HTTPError(e.http_code.value, str(e))
745 # def validate_password(realm, username, password):
746 # cherrypy.log("realm "+ str(realm))
747 # if username == "admin" and password == "admin":
752 def _start_service():
754 Callback function called when cherrypy.engine starts
755 Override configuration with env variables
756 Set database, storage, message configuration
757 Init database with admin/admin user password
759 cherrypy
.log
.error("Starting osm_nbi")
760 # update general cherrypy configuration
763 engine_config
= cherrypy
.tree
.apps
['/osm'].config
764 for k
, v
in environ
.items():
765 if not k
.startswith("OSMNBI_"):
767 k1
, _
, k2
= k
[7:].lower().partition("_")
771 # update static configuration
772 if k
== 'OSMNBI_STATIC_DIR':
773 engine_config
["/static"]['tools.staticdir.dir'] = v
774 engine_config
["/static"]['tools.staticdir.on'] = True
775 elif k
== 'OSMNBI_SOCKET_PORT' or k
== 'OSMNBI_SERVER_PORT':
776 update_dict
['server.socket_port'] = int(v
)
777 elif k
== 'OSMNBI_SOCKET_HOST' or k
== 'OSMNBI_SERVER_HOST':
778 update_dict
['server.socket_host'] = v
779 elif k1
in ("server", "test", "auth", "log"):
780 update_dict
[k1
+ '.' + k2
] = v
781 elif k1
in ("message", "database", "storage", "authentication"):
782 # k2 = k2.replace('_', '.')
783 if k2
in ("port", "db_port"):
784 engine_config
[k1
][k2
] = int(v
)
786 engine_config
[k1
][k2
] = v
788 except ValueError as e
:
789 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
790 except Exception as e
:
791 cherrypy
.log
.warn("skipping environ '{}' on exception '{}'".format(k
, e
))
794 cherrypy
.config
.update(update_dict
)
795 engine_config
["global"].update(update_dict
)
798 log_format_simple
= "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
799 log_formatter_simple
= logging
.Formatter(log_format_simple
, datefmt
='%Y-%m-%dT%H:%M:%S')
800 logger_server
= logging
.getLogger("cherrypy.error")
801 logger_access
= logging
.getLogger("cherrypy.access")
802 logger_cherry
= logging
.getLogger("cherrypy")
803 logger_nbi
= logging
.getLogger("nbi")
805 if "log.file" in engine_config
["global"]:
806 file_handler
= logging
.handlers
.RotatingFileHandler(engine_config
["global"]["log.file"],
807 maxBytes
=100e6
, backupCount
=9, delay
=0)
808 file_handler
.setFormatter(log_formatter_simple
)
809 logger_cherry
.addHandler(file_handler
)
810 logger_nbi
.addHandler(file_handler
)
812 for format_
, logger
in {"nbi.server": logger_server
,
813 "nbi.access": logger_access
,
814 "%(name)s %(filename)s:%(lineno)s": logger_nbi
816 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
817 log_formatter_cherry
= logging
.Formatter(log_format_cherry
, datefmt
='%Y-%m-%dT%H:%M:%S')
818 str_handler
= logging
.StreamHandler()
819 str_handler
.setFormatter(log_formatter_cherry
)
820 logger
.addHandler(str_handler
)
822 if engine_config
["global"].get("log.level"):
823 logger_cherry
.setLevel(engine_config
["global"]["log.level"])
824 logger_nbi
.setLevel(engine_config
["global"]["log.level"])
826 # logging other modules
827 for k1
, logname
in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
828 engine_config
[k1
]["logger_name"] = logname
829 logger_module
= logging
.getLogger(logname
)
830 if "logfile" in engine_config
[k1
]:
831 file_handler
= logging
.handlers
.RotatingFileHandler(engine_config
[k1
]["logfile"],
832 maxBytes
=100e6
, backupCount
=9, delay
=0)
833 file_handler
.setFormatter(log_formatter_simple
)
834 logger_module
.addHandler(file_handler
)
835 if "loglevel" in engine_config
[k1
]:
836 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
837 # TODO add more entries, e.g.: storage
838 cherrypy
.tree
.apps
['/osm'].root
.engine
.start(engine_config
)
839 cherrypy
.tree
.apps
['/osm'].root
.authenticator
.start(engine_config
)
841 cherrypy
.tree
.apps
['/osm'].root
.engine
.init_db(target_version
=database_version
)
842 cherrypy
.tree
.apps
['/osm'].root
.authenticator
.init_db(target_version
=auth_database_version
)
843 except (EngineException
, AuthException
):
845 # getenv('OSMOPENMANO_TENANT', None)
850 Callback function called when cherrypy.engine stops
851 TODO: Ending database connections.
853 cherrypy
.tree
.apps
['/osm'].root
.engine
.stop()
854 cherrypy
.log
.error("Stopping osm_nbi")
857 def nbi(config_file
):
860 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
861 # 'tools.sessions.on': True,
862 # 'tools.response_headers.on': True,
863 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
866 # cherrypy.Server.ssl_module = 'builtin'
867 # cherrypy.Server.ssl_certificate = "http/cert.pem"
868 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
869 # cherrypy.Server.thread_pool = 10
870 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
872 # cherrypy.config.update({'tools.auth_basic.on': True,
873 # 'tools.auth_basic.realm': 'localhost',
874 # 'tools.auth_basic.checkpassword': validate_password})
875 cherrypy
.engine
.subscribe('start', _start_service
)
876 cherrypy
.engine
.subscribe('stop', _stop_service
)
877 cherrypy
.quickstart(Server(), '/osm', config_file
)
881 print("""Usage: {} [options]
882 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
883 -h|--help: shows this help
884 """.format(sys
.argv
[0]))
885 # --log-socket-host HOST: send logs to this host")
886 # --log-socket-port PORT: send logs using this port (default: 9022)")
889 if __name__
== '__main__':
891 # load parameters and configuration
892 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvc:", ["config=", "help"])
893 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
896 if o
in ("-h", "--help"):
899 elif o
in ("-c", "--config"):
901 # elif o == "--log-socket-port":
902 # log_socket_port = a
903 # elif o == "--log-socket-host":
904 # log_socket_host = a
905 # elif o == "--log-file":
908 assert False, "Unhandled option"
910 if not path
.isfile(config_file
):
911 print("configuration file '{}' that not exist".format(config_file
), file=sys
.stderr
)
914 for config_file
in (__file__
[:__file__
.rfind(".")] + ".cfg", "./nbi.cfg", "/etc/osm/nbi.cfg"):
915 if path
.isfile(config_file
):
918 print("No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", file=sys
.stderr
)
921 except getopt
.GetoptError
as e
:
922 print(str(e
), file=sys
.stderr
)