2 # -*- coding: utf-8 -*-
8 import html_out
as html
10 from engine
import Engine
, EngineException
11 from osm_common
.dbbase
import DbException
12 from osm_common
.fsbase
import FsException
13 from osm_common
.msgbase
import MsgException
14 from base64
import standard_b64decode
15 #from os import getenv
16 from http
import HTTPStatus
17 #from http.client import responses as http_responses
18 from codecs
import getreader
19 from os
import environ
21 __author__
= "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
23 # TODO consider to remove and provide version using the static version file
25 version_date
= "Apr 2018"
26 database_version
= '1.0'
29 North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
30 URL: /osm GET POST PUT DELETE PATCH
32 /ns_descriptors_content O O
38 /artifacts[/<artifactPath>] O
46 /vnf_packages_content O O
50 /package_content O5 O5
53 /artifacts[/<artifactPath>] O5
58 /ns_instances_content O O
81 /vims_accounts (also vims for compatibility) O O
87 <attrName>[.<attrName>...]*[.<op>]=<value>[,<value>...]&...
88 op: "eq"(or empty to one or the values) | "neq" (to any of the values) | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
89 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
90 (none) … same as “exclude_default”
91 all_fields … all attributes.
92 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not conditionally mandatory, and that are not provided in <list>.
93 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that are not conditionally mandatory, and that are provided in <list>.
94 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for the particular resource
95 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the present specification for the particular resource, but that are not part of <list>
96 Header field name Reference Example Descriptions
97 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
98 This header field shall be present if the response is expected to have a non-empty message body.
99 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
100 This header field shall be present if the request has a non-empty message body.
101 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request. Details are specified in clause 4.5.3.
102 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
103 Header field name Reference Example Descriptions
104 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
105 This header field shall be present if the response has a non-empty message body.
106 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a new resource has been created.
107 This header field shall be present if the response status code is 201 or 3xx.
108 In the present document this header field is also used if the response status code is 202 and a new resource was created.
109 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization token.
110 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for certain resources.
111 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the response, and the total length of the file.
112 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
116 120 Used to indicate how long the user agent ought to wait before making a follow-up request.
117 It can be used with 503 responses.
118 The value of this field can be an HTTP-date or a number of seconds to delay after the response is received.
120 #TODO http header for partial uploads: Content-Range: "bytes 0-1199/15000". Id is returned first time and send in following chunks
124 class NbiException(Exception):
126 def __init__(self
, message
, http_code
=HTTPStatus
.METHOD_NOT_ALLOWED
):
127 Exception.__init
__(self
, message
)
128 self
.http_code
= http_code
131 class Server(object):
133 # to decode bytes to str
134 reader
= getreader("utf-8")
138 self
.engine
= Engine()
139 self
.valid_methods
= { # contains allowed URL and methods
142 "tokens": {"METHODS": ("GET", "POST", "DELETE"),
143 "<ID>": { "METHODS": ("GET", "DELETE")}
145 "users": {"METHODS": ("GET", "POST"),
146 "<ID>": {"METHODS": ("GET", "POST", "DELETE")}
148 "projects": {"METHODS": ("GET", "POST"),
149 "<ID>": {"METHODS": ("GET", "DELETE")}
151 "vims": {"METHODS": ("GET", "POST"),
152 "<ID>": {"METHODS": ("GET", "DELETE")}
154 "vim_accounts": {"METHODS": ("GET", "POST"),
155 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH")}
157 "sdns": {"METHODS": ("GET", "POST"),
158 "<ID>": {"METHODS": ("GET", "DELETE", "PATCH")}
164 "ns_descriptors_content": { "METHODS": ("GET", "POST"),
165 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
167 "ns_descriptors": { "METHODS": ("GET", "POST"),
168 "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH",
169 "nsd_content": { "METHODS": ("GET", "PUT")},
170 "nsd": {"METHODS": "GET"}, # descriptor inside package
171 "artifacts": {"*": {"METHODS": "GET"}}
175 "pnf_descriptors": {"TODO": ("GET", "POST"),
176 "<ID>": {"TODO": ("GET", "DELETE", "PATCH"),
177 "pnfd_content": {"TODO": ("GET", "PUT")}
180 "subscriptions": {"TODO": ("GET", "POST"),
181 "<ID>": {"TODO": ("GET", "DELETE"),}
187 "vnf_packages_content": { "METHODS": ("GET", "POST"),
188 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
190 "vnf_packages": { "METHODS": ("GET", "POST"),
191 "<ID>": { "METHODS": ("GET", "DELETE"), "TODO": "PATCH", # GET: vnfPkgInfo
192 "package_content": { "METHODS": ("GET", "PUT"), # package
193 "upload_from_uri": {"TODO": "POST"}
195 "vnfd": {"METHODS": "GET"}, # descriptor inside package
196 "artifacts": {"*": {"METHODS": "GET"}}
200 "subscriptions": {"TODO": ("GET", "POST"),
201 "<ID>": {"TODO": ("GET", "DELETE"),}
207 "ns_instances_content": {"METHODS": ("GET", "POST"),
208 "<ID>": {"METHODS": ("GET", "DELETE")}
210 "ns_instances": {"METHODS": ("GET", "POST"),
211 "<ID>": {"TODO": ("GET", "DELETE"),
212 "scale": {"TODO": "POST"},
213 "terminate": {"METHODS": "POST"},
214 "instantiate": {"METHODS": "POST"},
215 "action": {"METHODS": "POST"},
218 "ns_lcm_op_occs": {"METHODS": "GET",
219 "<ID>": {"METHODS": "GET"},
221 "vnfrs": {"METHODS": ("GET"),
222 "<ID>": {"METHODS": ("GET")}
228 def _authorization(self
):
232 # 1. Get token Authorization bearer
233 auth
= cherrypy
.request
.headers
.get("Authorization")
235 auth_list
= auth
.split(" ")
236 if auth_list
[0].lower() == "bearer":
237 token
= auth_list
[-1]
238 elif auth_list
[0].lower() == "basic":
239 user_passwd64
= auth_list
[-1]
241 if cherrypy
.session
.get("Authorization"):
242 # 2. Try using session before request a new token. If not, basic authentication will generate
243 token
= cherrypy
.session
.get("Authorization")
244 if token
== "logout":
245 token
= None # force Unauthorized response to insert user pasword again
246 elif user_passwd64
and cherrypy
.request
.config
.get("auth.allow_basic_authentication"):
247 # 3. Get new token from user password
251 user_passwd
= standard_b64decode(user_passwd64
).decode()
252 user
, _
, passwd
= user_passwd
.partition(":")
255 outdata
= self
.engine
.new_token(None, {"username": user
, "password": passwd
})
256 token
= outdata
["id"]
257 cherrypy
.session
['Authorization'] = token
258 # 4. Get token from cookie
260 # auth_cookie = cherrypy.request.cookie.get("Authorization")
262 # token = auth_cookie.value
263 return self
.engine
.authorize(token
)
264 except EngineException
as e
:
265 if cherrypy
.session
.get('Authorization'):
266 del cherrypy
.session
['Authorization']
267 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Bearer realm="{}"'.format(e
)
270 def _format_in(self
, kwargs
):
273 if cherrypy
.request
.body
.length
:
274 error_text
= "Invalid input format "
276 if "Content-Type" in cherrypy
.request
.headers
:
277 if "application/json" in cherrypy
.request
.headers
["Content-Type"]:
278 error_text
= "Invalid json format "
279 indata
= json
.load(self
.reader(cherrypy
.request
.body
))
280 elif "application/yaml" in cherrypy
.request
.headers
["Content-Type"]:
281 error_text
= "Invalid yaml format "
282 indata
= yaml
.load(cherrypy
.request
.body
)
283 elif "application/binary" in cherrypy
.request
.headers
["Content-Type"] or \
284 "application/gzip" in cherrypy
.request
.headers
["Content-Type"] or \
285 "application/zip" in cherrypy
.request
.headers
["Content-Type"] or \
286 "text/plain" in cherrypy
.request
.headers
["Content-Type"]:
287 indata
= cherrypy
.request
.body
# .read()
288 elif "multipart/form-data" in cherrypy
.request
.headers
["Content-Type"]:
289 if "descriptor_file" in kwargs
:
290 filecontent
= kwargs
.pop("descriptor_file")
291 if not filecontent
.file:
292 raise NbiException("empty file or content", HTTPStatus
.BAD_REQUEST
)
293 indata
= filecontent
.file # .read()
294 if filecontent
.content_type
.value
:
295 cherrypy
.request
.headers
["Content-Type"] = filecontent
.content_type
.value
297 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
298 # "Only 'Content-Type' of type 'application/json' or
299 # 'application/yaml' for input format are available")
300 error_text
= "Invalid yaml format "
301 indata
= yaml
.load(cherrypy
.request
.body
)
303 error_text
= "Invalid yaml format "
304 indata
= yaml
.load(cherrypy
.request
.body
)
309 if cherrypy
.request
.headers
.get("Query-String-Format") == "yaml":
312 for k
, v
in kwargs
.items():
313 if isinstance(v
, str):
318 kwargs
[k
] = yaml
.load(v
)
321 elif k
.endswith(".gt") or k
.endswith(".lt") or k
.endswith(".gte") or k
.endswith(".lte"):
329 elif v
.find(",") > 0:
330 kwargs
[k
] = v
.split(",")
331 elif isinstance(v
, (list, tuple)):
332 for index
in range(0, len(v
)):
337 v
[index
] = yaml
.load(v
[index
])
342 except (ValueError, yaml
.YAMLError
) as exc
:
343 raise NbiException(error_text
+ str(exc
), HTTPStatus
.BAD_REQUEST
)
344 except KeyError as exc
:
345 raise NbiException("Query string error: " + str(exc
), HTTPStatus
.BAD_REQUEST
)
348 def _format_out(data
, session
=None, _format
=None):
350 return string of dictionary data according to requested json, yaml, xml. By default json
351 :param data: response to be sent. Can be a dict, text or file
353 :param _format: The format to be set as Content-Type ir data is a file
356 accept
= cherrypy
.request
.headers
.get("Accept")
358 if accept
and "text/html" in accept
:
359 return html
.format(data
, cherrypy
.request
, cherrypy
.response
, session
)
360 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
362 elif hasattr(data
, "read"): # file object
364 cherrypy
.response
.headers
["Content-Type"] = _format
365 elif "b" in data
.mode
: # binariy asssumig zip
366 cherrypy
.response
.headers
["Content-Type"] = 'application/zip'
368 cherrypy
.response
.headers
["Content-Type"] = 'text/plain'
369 # TODO check that cherrypy close file. If not implement pending things to close per thread next
372 if "application/json" in accept
:
373 cherrypy
.response
.headers
["Content-Type"] = 'application/json; charset=utf-8'
374 a
= json
.dumps(data
, indent
=4) + "\n"
375 return a
.encode("utf8")
376 elif "text/html" in accept
:
377 return html
.format(data
, cherrypy
.request
, cherrypy
.response
, session
)
379 elif "application/yaml" in accept
or "*/*" in accept
or "text/plain" in accept
:
382 raise cherrypy
.HTTPError(HTTPStatus
.NOT_ACCEPTABLE
.value
,
383 "Only 'Accept' of type 'application/json' or 'application/yaml' "
384 "for output format are available")
385 cherrypy
.response
.headers
["Content-Type"] = 'application/yaml'
386 return yaml
.safe_dump(data
, explicit_start
=True, indent
=4, default_flow_style
=False, tags
=False,
387 encoding
='utf-8', allow_unicode
=True) # , canonical=True, default_style='"'
390 def index(self
, *args
, **kwargs
):
393 if cherrypy
.request
.method
== "GET":
394 session
= self
._authorization
()
395 outdata
= "Index page"
397 raise cherrypy
.HTTPError(HTTPStatus
.METHOD_NOT_ALLOWED
.value
,
398 "Method {} not allowed for tokens".format(cherrypy
.request
.method
))
400 return self
._format
_out
(outdata
, session
)
402 except EngineException
as e
:
403 cherrypy
.log("index Exception {}".format(e
))
404 cherrypy
.response
.status
= e
.http_code
.value
405 return self
._format
_out
("Welcome to OSM!", session
)
408 def version(self
, *args
, **kwargs
):
409 # TODO consider to remove and provide version using the static version file
410 global __version__
, version_date
412 if cherrypy
.request
.method
!= "GET":
413 raise NbiException("Only method GET is allowed", HTTPStatus
.METHOD_NOT_ALLOWED
)
415 raise NbiException("Invalid URL or query string for version", HTTPStatus
.METHOD_NOT_ALLOWED
)
416 return __version__
+ " " + version_date
417 except NbiException
as e
:
418 cherrypy
.response
.status
= e
.http_code
.value
420 "code": e
.http_code
.name
,
421 "status": e
.http_code
.value
,
424 return self
._format
_out
(problem_details
, None)
427 def token(self
, method
, token_id
=None, kwargs
=None):
429 # self.engine.load_dbase(cherrypy.request.app.config)
430 indata
= self
._format
_in
(kwargs
)
431 if not isinstance(indata
, dict):
432 raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus
.BAD_REQUEST
)
435 session
= self
._authorization
()
437 outdata
= self
.engine
.get_token(session
, token_id
)
439 outdata
= self
.engine
.get_token_list(session
)
440 elif method
== "POST":
442 session
= self
._authorization
()
446 indata
.update(kwargs
)
447 outdata
= self
.engine
.new_token(session
, indata
, cherrypy
.request
.remote
)
449 cherrypy
.session
['Authorization'] = outdata
["_id"]
450 self
._set
_location
_header
("admin", "v1", "tokens", outdata
["_id"])
451 # cherrypy.response.cookie["Authorization"] = outdata["id"]
452 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
453 elif method
== "DELETE":
454 if not token_id
and "id" in kwargs
:
455 token_id
= kwargs
["id"]
457 session
= self
._authorization
()
458 token_id
= session
["_id"]
459 outdata
= self
.engine
.del_token(token_id
)
462 cherrypy
.session
['Authorization'] = "logout"
463 # cherrypy.response.cookie["Authorization"] = token_id
464 # cherrypy.response.cookie["Authorization"]['expires'] = 0
466 raise NbiException("Method {} not allowed for token".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
467 return self
._format
_out
(outdata
, session
)
468 except (NbiException
, EngineException
, DbException
) as e
:
469 cherrypy
.log("tokens Exception {}".format(e
))
470 cherrypy
.response
.status
= e
.http_code
.value
472 "code": e
.http_code
.name
,
473 "status": e
.http_code
.value
,
476 return self
._format
_out
(problem_details
, session
)
479 def test(self
, *args
, **kwargs
):
481 if args
and args
[0] == "help":
482 return "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nprune\nlogin\nlogin2\n"\
483 "sleep/<time>\nmessage/topic\n</pre></html>"
485 elif args
and args
[0] == "init":
487 # self.engine.load_dbase(cherrypy.request.app.config)
488 self
.engine
.create_admin()
489 return "Done. User 'admin', password 'admin' created"
491 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
492 return self
._format
_out
("Database already initialized")
493 elif args
and args
[0] == "file":
494 return cherrypy
.lib
.static
.serve_file(cherrypy
.tree
.apps
['/osm'].config
["storage"]["path"] + "/" + args
[1],
495 "text/plain", "attachment")
496 elif args
and args
[0] == "file2":
497 f_path
= cherrypy
.tree
.apps
['/osm'].config
["storage"]["path"] + "/" + args
[1]
498 f
= open(f_path
, "r")
499 cherrypy
.response
.headers
["Content-type"] = "text/plain"
502 elif len(args
) == 2 and args
[0] == "db-clear":
503 return self
.engine
.del_item_list({"project_id": "admin"}, args
[1], {})
504 elif args
and args
[0] == "prune":
505 return self
.engine
.prune()
506 elif args
and args
[0] == "login":
507 if not cherrypy
.request
.headers
.get("Authorization"):
508 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Basic realm="Access to OSM site", charset="UTF-8"'
509 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
510 elif args
and args
[0] == "login2":
511 if not cherrypy
.request
.headers
.get("Authorization"):
512 cherrypy
.response
.headers
["WWW-Authenticate"] = 'Bearer realm="Access to OSM site"'
513 cherrypy
.response
.status
= HTTPStatus
.UNAUTHORIZED
.value
514 elif args
and args
[0] == "sleep":
517 sleep_time
= int(args
[1])
519 cherrypy
.response
.status
= HTTPStatus
.FORBIDDEN
.value
520 return self
._format
_out
("Database already initialized")
521 thread_info
= cherrypy
.thread_data
523 time
.sleep(sleep_time
)
525 elif len(args
) >= 2 and args
[0] == "message":
527 return_text
= "<html><pre>{} ->\n".format(topic
)
529 if cherrypy
.request
.method
== 'POST':
530 to_send
= yaml
.load(cherrypy
.request
.body
)
531 for k
, v
in to_send
.items():
532 self
.engine
.msg
.write(topic
, k
, v
)
533 return_text
+= " {}: {}\n".format(k
, v
)
534 elif cherrypy
.request
.method
== 'GET':
535 for k
, v
in kwargs
.items():
536 self
.engine
.msg
.write(topic
, k
, yaml
.load(v
))
537 return_text
+= " {}: {}\n".format(k
, yaml
.load(v
))
538 except Exception as e
:
539 return_text
+= "Error: " + str(e
)
540 return_text
+= "</pre></html>\n"
544 "<html><pre>\nheaders:\n args: {}\n".format(args
) +
545 " kwargs: {}\n".format(kwargs
) +
546 " headers: {}\n".format(cherrypy
.request
.headers
) +
547 " path_info: {}\n".format(cherrypy
.request
.path_info
) +
548 " query_string: {}\n".format(cherrypy
.request
.query_string
) +
549 " session: {}\n".format(cherrypy
.session
) +
550 " cookie: {}\n".format(cherrypy
.request
.cookie
) +
551 " method: {}\n".format(cherrypy
.request
.method
) +
552 " session: {}\n".format(cherrypy
.session
.get('fieldname')) +
554 return_text
+= " length: {}\n".format(cherrypy
.request
.body
.length
)
555 if cherrypy
.request
.body
.length
:
556 return_text
+= " content: {}\n".format(
557 str(cherrypy
.request
.body
.read(int(cherrypy
.request
.headers
.get('Content-Length', 0)))))
559 return_text
+= "thread: {}\n".format(thread_info
)
560 return_text
+= "</pre></html>"
563 def _check_valid_url_method(self
, method
, *args
):
565 raise NbiException("URL must contain at least 'topic/version/item'", HTTPStatus
.METHOD_NOT_ALLOWED
)
567 reference
= self
.valid_methods
571 if not isinstance(reference
, dict):
572 raise NbiException("URL contains unexpected extra items '{}'".format(arg
),
573 HTTPStatus
.METHOD_NOT_ALLOWED
)
576 reference
= reference
[arg
]
577 elif "<ID>" in reference
:
578 reference
= reference
["<ID>"]
579 elif "*" in reference
:
580 reference
= reference
["*"]
583 raise NbiException("Unexpected URL item {}".format(arg
), HTTPStatus
.METHOD_NOT_ALLOWED
)
584 if "TODO" in reference
and method
in reference
["TODO"]:
585 raise NbiException("Method {} not supported yet for this URL".format(method
), HTTPStatus
.NOT_IMPLEMENTED
)
586 elif "METHODS" in reference
and not method
in reference
["METHODS"]:
587 raise NbiException("Method {} not supported for this URL".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
591 def _set_location_header(topic
, version
, item
, id):
593 Insert response header Location with the URL of created item base on URL params
600 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
601 cherrypy
.response
.headers
["Location"] = "/osm/{}/{}/{}/{}".format(topic
, version
, item
, id)
605 def default(self
, topic
=None, version
=None, item
=None, _id
=None, item2
=None, *args
, **kwargs
):
613 if not topic
or not version
or not item
:
614 raise NbiException("URL must contain at least 'topic/version/item'", HTTPStatus
.METHOD_NOT_ALLOWED
)
615 if topic
not in ("admin", "vnfpkgm", "nsd", "nslcm"):
616 raise NbiException("URL topic '{}' not supported".format(topic
), HTTPStatus
.METHOD_NOT_ALLOWED
)
618 raise NbiException("URL version '{}' not supported".format(version
), HTTPStatus
.METHOD_NOT_ALLOWED
)
620 if kwargs
and "METHOD" in kwargs
and kwargs
["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH"):
621 method
= kwargs
.pop("METHOD")
623 method
= cherrypy
.request
.method
625 self
._check
_valid
_url
_method
(method
, topic
, version
, item
, _id
, item2
, *args
)
627 if topic
== "admin" and item
== "tokens":
628 return self
.token(method
, _id
, kwargs
)
630 # self.engine.load_dbase(cherrypy.request.app.config)
631 session
= self
._authorization
()
632 indata
= self
._format
_in
(kwargs
)
634 if item
== "subscriptions":
635 engine_item
= topic
+ "_" + item
641 elif topic
== "vnfpkgm":
642 engine_item
= "vnfds"
643 elif topic
== "nslcm":
645 if item
== "ns_lcm_op_occs":
646 engine_item
= "nslcmops"
648 engine_item
= "vnfrs"
649 if engine_item
== "vims": # TODO this is for backward compatibility, it will remove in the future
650 engine_item
= "vim_accounts"
653 if item2
in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd"):
654 if item2
in ("vnfd", "nsd"):
658 elif item2
== "artifacts":
662 file, _format
= self
.engine
.get_file(session
, engine_item
, _id
, path
,
663 cherrypy
.request
.headers
.get("Accept"))
666 outdata
= self
.engine
.get_item_list(session
, engine_item
, kwargs
)
668 outdata
= self
.engine
.get_item(session
, engine_item
, _id
)
669 elif method
== "POST":
670 if item
in ("ns_descriptors_content", "vnf_packages_content"):
671 _id
= cherrypy
.request
.headers
.get("Transaction-Id")
673 _id
= self
.engine
.new_item(session
, engine_item
, {}, None, cherrypy
.request
.headers
)
674 rollback
= {"session": session
, "item": engine_item
, "_id": _id
, "force": True}
675 completed
= self
.engine
.upload_content(session
, engine_item
, _id
, indata
, kwargs
, cherrypy
.request
.headers
)
677 self
._set
_location
_header
(topic
, version
, item
, _id
)
679 cherrypy
.response
.headers
["Transaction-Id"] = _id
680 outdata
= {"id": _id
}
681 elif item
== "ns_instances_content":
682 _id
= self
.engine
.new_item(session
, engine_item
, indata
, kwargs
)
683 rollback
= {"session": session
, "item": engine_item
, "_id": _id
, "force": True}
684 self
.engine
.ns_action(session
, _id
, "instantiate", {}, None)
685 self
._set
_location
_header
(topic
, version
, item
, _id
)
686 outdata
= {"id": _id
}
687 elif item
== "ns_instances" and item2
:
688 _id
= self
.engine
.ns_action(session
, _id
, item2
, indata
, kwargs
)
689 self
._set
_location
_header
(topic
, version
, "ns_lcm_op_occs", _id
)
690 outdata
= {"id": _id
}
691 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
693 _id
= self
.engine
.new_item(session
, engine_item
, indata
, kwargs
, cherrypy
.request
.headers
)
694 self
._set
_location
_header
(topic
, version
, item
, _id
)
695 outdata
= {"id": _id
}
696 # TODO form NsdInfo when item in ("ns_descriptors", "vnf_packages")
697 cherrypy
.response
.status
= HTTPStatus
.CREATED
.value
699 elif method
== "DELETE":
701 outdata
= self
.engine
.del_item_list(session
, engine_item
, kwargs
)
702 cherrypy
.response
.status
= HTTPStatus
.OK
.value
703 else: # len(args) > 1
704 if item
== "ns_instances_content":
705 opp_id
= self
.engine
.ns_action(session
, _id
, "terminate", {"autoremove": True}, None)
706 outdata
= {"_id": opp_id
}
707 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
709 force
= kwargs
.get("FORCE")
710 self
.engine
.del_item(session
, engine_item
, _id
, force
)
711 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
712 if engine_item
in ("vim_accounts", "sdns"):
713 cherrypy
.response
.status
= HTTPStatus
.ACCEPTED
.value
715 elif method
== "PUT":
716 if not indata
and not kwargs
:
717 raise NbiException("Nothing to update. Provide payload and/or query string",
718 HTTPStatus
.BAD_REQUEST
)
719 if item2
in ("nsd_content", "package_content"):
720 completed
= self
.engine
.upload_content(session
, engine_item
, _id
, indata
, kwargs
, cherrypy
.request
.headers
)
722 cherrypy
.response
.headers
["Transaction-Id"] = id
723 cherrypy
.response
.status
= HTTPStatus
.NO_CONTENT
.value
726 outdata
= {"id": self
.engine
.edit_item(session
, engine_item
, _id
, indata
, kwargs
)}
727 elif method
== "PATCH":
728 if not indata
and not kwargs
:
729 raise NbiException("Nothing to update. Provide payload and/or query string",
730 HTTPStatus
.BAD_REQUEST
)
731 outdata
= {"id": self
.engine
.edit_item(session
, engine_item
, _id
, indata
, kwargs
)}
733 raise NbiException("Method {} not allowed".format(method
), HTTPStatus
.METHOD_NOT_ALLOWED
)
734 return self
._format
_out
(outdata
, session
, _format
)
735 except (NbiException
, EngineException
, DbException
, FsException
, MsgException
) as e
:
736 cherrypy
.log("Exception {}".format(e
))
737 cherrypy
.response
.status
= e
.http_code
.value
738 if hasattr(outdata
, "close"): # is an open file
742 self
.engine
.del_item(**rollback
)
743 except Exception as e2
:
744 cherrypy
.log("Rollback Exception {}: {}".format(rollback
, e2
))
746 if isinstance(e
, MsgException
):
747 error_text
= "{} has been '{}' but other modules cannot be informed because an error on bus".format(
748 engine_item
[:-1], method
, error_text
)
750 "code": e
.http_code
.name
,
751 "status": e
.http_code
.value
,
754 return self
._format
_out
(problem_details
, session
)
755 # raise cherrypy.HTTPError(e.http_code.value, str(e))
758 # def validate_password(realm, username, password):
759 # cherrypy.log("realm "+ str(realm))
760 # if username == "admin" and password == "admin":
765 def _start_service():
767 Callback function called when cherrypy.engine starts
768 Override configuration with env variables
769 Set database, storage, message configuration
770 Init database with admin/admin user password
772 cherrypy
.log
.error("Starting osm_nbi")
773 # update general cherrypy configuration
776 engine_config
= cherrypy
.tree
.apps
['/osm'].config
777 for k
, v
in environ
.items():
778 if not k
.startswith("OSMNBI_"):
780 k1
, _
, k2
= k
[7:].lower().partition("_")
784 # update static configuration
785 if k
== 'OSMNBI_STATIC_DIR':
786 engine_config
["/static"]['tools.staticdir.dir'] = v
787 engine_config
["/static"]['tools.staticdir.on'] = True
788 elif k
== 'OSMNBI_SOCKET_PORT' or k
== 'OSMNBI_SERVER_PORT':
789 update_dict
['server.socket_port'] = int(v
)
790 elif k
== 'OSMNBI_SOCKET_HOST' or k
== 'OSMNBI_SERVER_HOST':
791 update_dict
['server.socket_host'] = v
793 update_dict
['server' + k2
] = v
794 # TODO add more entries
795 elif k1
in ("message", "database", "storage"):
797 engine_config
[k1
][k2
] = int(v
)
799 engine_config
[k1
][k2
] = v
800 except ValueError as e
:
801 cherrypy
.log
.error("Ignoring environ '{}': " + str(e
))
802 except Exception as e
:
803 cherrypy
.log
.warn("skipping environ '{}' on exception '{}'".format(k
, e
))
806 cherrypy
.config
.update(update_dict
)
809 log_format_simple
= "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
810 log_formatter_simple
= logging
.Formatter(log_format_simple
, datefmt
='%Y-%m-%dT%H:%M:%S')
811 logger_server
= logging
.getLogger("cherrypy.error")
812 logger_access
= logging
.getLogger("cherrypy.access")
813 logger_cherry
= logging
.getLogger("cherrypy")
814 logger_nbi
= logging
.getLogger("nbi")
816 if "logfile" in engine_config
["global"]:
817 file_handler
= logging
.handlers
.RotatingFileHandler(engine_config
["global"]["logfile"],
818 maxBytes
=100e6
, backupCount
=9, delay
=0)
819 file_handler
.setFormatter(log_formatter_simple
)
820 logger_cherry
.addHandler(file_handler
)
821 logger_nbi
.addHandler(file_handler
)
823 for format_
, logger
in {"nbi.server": logger_server
,
824 "nbi.access": logger_access
,
825 "%(name)s %(filename)s:%(lineno)s": logger_nbi
827 log_format_cherry
= "%(asctime)s %(levelname)s {} %(message)s".format(format_
)
828 log_formatter_cherry
= logging
.Formatter(log_format_cherry
, datefmt
='%Y-%m-%dT%H:%M:%S')
829 str_handler
= logging
.StreamHandler()
830 str_handler
.setFormatter(log_formatter_cherry
)
831 logger
.addHandler(str_handler
)
833 if engine_config
["global"].get("loglevel"):
834 logger_cherry
.setLevel(engine_config
["global"]["loglevel"])
835 logger_nbi
.setLevel(engine_config
["global"]["loglevel"])
837 # logging other modules
838 for k1
, logname
in {"message": "nbi.msg", "database": "nbi.db", "storage": "nbi.fs"}.items():
839 engine_config
[k1
]["logger_name"] = logname
840 logger_module
= logging
.getLogger(logname
)
841 if "logfile" in engine_config
[k1
]:
842 file_handler
= logging
.handlers
.RotatingFileHandler(engine_config
[k1
]["logfile"],
843 maxBytes
=100e6
, backupCount
=9, delay
=0)
844 file_handler
.setFormatter(log_formatter_simple
)
845 logger_module
.addHandler(file_handler
)
846 if "loglevel" in engine_config
[k1
]:
847 logger_module
.setLevel(engine_config
[k1
]["loglevel"])
848 # TODO add more entries, e.g.: storage
849 cherrypy
.tree
.apps
['/osm'].root
.engine
.start(engine_config
)
851 cherrypy
.tree
.apps
['/osm'].root
.engine
.init_db(target_version
=database_version
)
852 except EngineException
:
854 # getenv('OSMOPENMANO_TENANT', None)
859 Callback function called when cherrypy.engine stops
860 TODO: Ending database connections.
862 cherrypy
.tree
.apps
['/osm'].root
.engine
.stop()
863 cherrypy
.log
.error("Stopping osm_nbi")
868 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
869 # 'tools.sessions.on': True,
870 # 'tools.response_headers.on': True,
871 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
874 # cherrypy.Server.ssl_module = 'builtin'
875 # cherrypy.Server.ssl_certificate = "http/cert.pem"
876 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
877 # cherrypy.Server.thread_pool = 10
878 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
880 # cherrypy.config.update({'tools.auth_basic.on': True,
881 # 'tools.auth_basic.realm': 'localhost',
882 # 'tools.auth_basic.checkpassword': validate_password})
883 cherrypy
.engine
.subscribe('start', _start_service
)
884 cherrypy
.engine
.subscribe('stop', _stop_service
)
885 cherrypy
.quickstart(Server(), '/osm', "nbi.cfg")
888 if __name__
== '__main__':