from random import choice as random_choice
from uuid import uuid4
from hashlib import sha256, md5
-from osm_common.dbbase import DbException
+from osm_common.dbbase import DbException, deep_update
from osm_common.fsbase import FsException
from osm_common.msgbase import MsgException
from http import HTTPStatus
from time import time
-from copy import deepcopy
+from copy import deepcopy, copy
from validation import validate_input, ValidationError
__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
Exception.__init__(self, message)
-def _deep_update(dict_to_change, dict_reference):
+def get_iterable(input):
"""
- Modifies one dictionary with the information of the other following https://tools.ietf.org/html/rfc7396
- :param dict_to_change: Ends modified
- :param dict_reference: reference
- :return: none
+ Returns an iterable, in case input is None it just returns an empty tuple
+ :param input:
+ :return: iterable
"""
- for k in dict_reference:
- if dict_reference[k] is None: # None->Anything
- if k in dict_to_change:
- del dict_to_change[k]
- elif not isinstance(dict_reference[k], dict): # NotDict->Anything
- dict_to_change[k] = dict_reference[k]
- elif k not in dict_to_change: # Dict->Empty
- dict_to_change[k] = deepcopy(dict_reference[k])
- _deep_update(dict_to_change[k], dict_reference[k])
- elif isinstance(dict_to_change[k], dict): # Dict->Dict
- _deep_update(dict_to_change[k], dict_reference[k])
- else: # Dict->NotDict
- dict_to_change[k] = deepcopy(dict_reference[k])
- _deep_update(dict_to_change[k], dict_reference[k])
+ if input is None:
+ return ()
+ return input
class Engine(object):
clean_indata = clean_indata['userDefinedData']
return clean_indata
- def _check_dependencies_on_descriptor(self, session, item, descriptor_id):
+ def _check_project_dependencies(self, project_id):
+ """
+ Check if a project can be deleted
+ :param session:
+ :param _id:
+ :return:
+ """
+ # TODO Is it needed to check descriptors _admin.project_read/project_write??
+ _filter = {"projects": project_id}
+ if self.db.get_list("users", _filter):
+ raise EngineException("There are users that uses this project", http_code=HTTPStatus.CONFLICT)
+
+ def _check_dependencies_on_descriptor(self, session, item, descriptor_id, _id):
"""
Check that the descriptor to be deleded is not a dependency of others
:param session: client session information
:param item: can be vnfds, nsds
- :param descriptor_id: id of descriptor to be deleted
+ :param descriptor_id: id (provided by client) of descriptor to be deleted
+ :param _id: internal id of descriptor to be deleted
:return: None or raises exception
"""
if item == "vnfds":
_filter = {"constituent-vnfd.ANYINDEX.vnfd-id-ref": descriptor_id}
if self.get_item_list(session, "nsds", _filter):
raise EngineException("There are nsd that depends on this VNFD", http_code=HTTPStatus.CONFLICT)
+ if self.get_item_list(session, "vnfrs", {"vnfd-id": _id}):
+ raise EngineException("There are vnfr that depends on this VNFD", http_code=HTTPStatus.CONFLICT)
elif item == "nsds":
- _filter = {"nsdId": descriptor_id}
+ _filter = {"nsdId": _id}
if self.get_item_list(session, "nsrs", _filter):
raise EngineException("There are nsr that depends on this NSD", http_code=HTTPStatus.CONFLICT)
raise EngineException("Descriptor error at nsdId='{}' references a non exist nsd".format(nsd_id),
http_code=HTTPStatus.CONFLICT)
+ def _check_edition(self, session, item, indata, id, force=False):
+ if item == "users":
+ if indata.get("projects"):
+ if not session["admin"]:
+ raise EngineException("Needed admin privileges to edit user projects", HTTPStatus.UNAUTHORIZED)
+ if indata.get("password"):
+ # regenerate salt and encrypt password
+ salt = uuid4().hex
+ indata["_admin"] = {"salt": salt}
+ indata["password"] = sha256(indata["password"].encode('utf-8') + salt.encode('utf-8')).hexdigest()
+
def _validate_new_data(self, session, item, indata, id=None, force=False):
if item == "users":
- if not indata.get("username"):
- raise EngineException("missing 'username'", HTTPStatus.UNPROCESSABLE_ENTITY)
- if not indata.get("password"):
- raise EngineException("missing 'password'", HTTPStatus.UNPROCESSABLE_ENTITY)
- if not indata.get("projects"):
- raise EngineException("missing 'projects'", HTTPStatus.UNPROCESSABLE_ENTITY)
# check username not exists
- if self.db.get_one(item, {"username": indata.get("username")}, fail_on_empty=False, fail_on_more=False):
+ if not id and self.db.get_one(item, {"username": indata.get("username")}, fail_on_empty=False,
+ fail_on_more=False):
raise EngineException("username '{}' exists".format(indata["username"]), HTTPStatus.CONFLICT)
+ # check projects
+ if not force:
+ for p in indata["projects"]:
+ if p == "admin":
+ continue
+ if not self.db.get_one("projects", {"_id": p}, fail_on_empty=False, fail_on_more=False):
+ raise EngineException("project '{}' does not exists".format(p), HTTPStatus.CONFLICT)
elif item == "projects":
if not indata.get("name"):
raise EngineException("missing 'name'")
# check name not exists
- if self.db.get_one(item, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False):
+ if not id and self.db.get_one(item, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False):
raise EngineException("name '{}' exists".format(indata["name"]), HTTPStatus.CONFLICT)
elif item in ("vnfds", "nsds"):
filter = {"id": indata["id"]}
:param indata: descriptor with the parameters of the operation
:return: None
"""
+ vnfds = {}
+ vim_accounts = []
+ nsd = nsr["nsd"]
+
+ def check_valid_vnf_member_index(member_vnf_index):
+ for vnf in nsd["constituent-vnfd"]:
+ if member_vnf_index == vnf["member-vnf-index"]:
+ vnfd_id = vnf["vnfd-id-ref"]
+ if vnfd_id not in vnfds:
+ vnfds[vnfd_id] = self.db.get_one("vnfds", {"id": vnfd_id})
+ return vnfds[vnfd_id]
+ else:
+ raise EngineException("Invalid parameter member_vnf_index='{}' is not one of the "
+ "nsd:constituent-vnfd".format(member_vnf_index))
+
+ def check_valid_vim_account(vim_account):
+ if vim_account in vim_accounts:
+ return
+ try:
+ self.db.get_one("vim_accounts", {"_id": vim_account})
+ except Exception:
+ raise EngineException("Invalid vimAccountId='{}' not present".format(vim_account))
+ vim_accounts.append(vim_account)
+
if operation == "action":
+ # check vnf_member_index
if indata.get("vnf_member_index"):
indata["member_vnf_index"] = indata.pop("vnf_member_index") # for backward compatibility
- for vnf in nsr["nsd"]["constituent-vnfd"]:
- if indata["member_vnf_index"] == vnf["member-vnf-index"]:
- # TODO get vnfd, check primitives
+ if not indata.get("member_vnf_index"):
+ raise EngineException("Missing 'member_vnf_index' parameter")
+ vnfd = check_valid_vnf_member_index(indata["member_vnf_index"])
+ # check primitive
+ for config_primitive in get_iterable(vnfd.get("vnf-configuration", {}).get("config-primitive")):
+ if indata["primitive"] == config_primitive["name"]:
+ # check needed primitive_params are provided
+ if indata.get("primitive_params"):
+ in_primitive_params_copy = copy(indata["primitive_params"])
+ else:
+ in_primitive_params_copy = {}
+ for paramd in get_iterable(config_primitive.get("parameter")):
+ if paramd["name"] in in_primitive_params_copy:
+ del in_primitive_params_copy[paramd["name"]]
+ elif not paramd.get("default-value"):
+ raise EngineException("Needed parameter {} not provided for primitive '{}'".format(
+ paramd["name"], indata["primitive"]))
+ # check no extra primitive params are provided
+ if in_primitive_params_copy:
+ raise EngineException("parameter/s '{}' not present at vnfd for primitive '{}'".format(
+ list(in_primitive_params_copy.keys()), indata["primitive"]))
+ break
+ else:
+ raise EngineException("Invalid primitive '{}' is not present at vnfd".format(indata["primitive"]))
+ if operation == "scale":
+ vnfd = check_valid_vnf_member_index(indata["scaleVnfData"]["scaleByStepData"]["member-vnf-index"])
+ for scaling_group in get_iterable(vnfd.get("scaling-group-descriptor")):
+ if indata["scaleVnfData"]["scaleByStepData"]["scaling-group-descriptor"] == scaling_group["name"]:
break
else:
- raise EngineException("Invalid parameter member_vnf_index='{}' is not one of the nsd "
- "constituent-vnfd".format(indata["member_vnf_index"]))
+ raise EngineException("Invalid scaleVnfData:scaleByStepData:scaling-group-descriptor '{}' is not "
+ "present at vnfd:scaling-group-descriptor".format(
+ indata["scaleVnfData"]["scaleByStepData"]["scaling-group-descriptor"]))
+ if operation == "instantiate":
+ # check vim_account
+ check_valid_vim_account(indata["vimAccountId"])
+ for in_vnf in get_iterable(indata.get("vnf")):
+ vnfd = check_valid_vnf_member_index(in_vnf["member-vnf-index"])
+ if in_vnf.get("vimAccountId"):
+ check_valid_vim_account(in_vnf["vimAccountId"])
+ for in_vdu in get_iterable(in_vnf.get("vdu")):
+ for vdud in get_iterable(vnfd.get("vdu")):
+ if vdud["id"] == in_vdu["id"]:
+ for volume in get_iterable(in_vdu.get("volume")):
+ for volumed in get_iterable(vdud.get("volumes")):
+ if volumed["name"] == volume["name"]:
+ break
+ else:
+ raise EngineException("Invalid parameter vnf[member-vnf-index='{}']:vdu[id='{}']:"
+ "volume:name='{}' is not present at vnfd:vdu:volumes list".
+ format(in_vnf["member-vnf-index"], in_vdu["id"],
+ volume["name"]))
+ break
+ else:
+ raise EngineException("Invalid parameter vnf[member-vnf-index='{}']:vdu:id='{}' is not "
+ "present at vnfd".format(in_vnf["member-vnf-index"], in_vdu["id"]))
+
+ for in_internal_vld in get_iterable(in_vnf.get("internal-vld")):
+ for internal_vldd in get_iterable(vnfd.get("internal-vld")):
+ if in_internal_vld["name"] == internal_vldd["name"] or \
+ in_internal_vld["name"] == internal_vldd["id"]:
+ break
+ else:
+ raise EngineException("Invalid parameter vnf[member-vnf-index='{}']:internal-vld:name='{}'"
+ " is not present at vnfd '{}'".format(in_vnf["member-vnf-index"],
+ in_internal_vld["name"],
+ vnfd["id"]))
+ for in_vld in get_iterable(indata.get("vld")):
+ for vldd in get_iterable(nsd.get("vld")):
+ if in_vld["name"] == vldd["name"] or in_vld["name"] == vldd["id"]:
+ break
+ else:
+ raise EngineException("Invalid parameter vld:name='{}' is not present at nsd:vld".format(
+ in_vld["name"]))
def _format_new_data(self, session, item, indata):
now = time()
if file_pkg:
file_pkg.close()
- def new_nsr(self, session, ns_request):
+ def new_nsr(self, rollback, session, ns_request):
"""
Creates a new nsr into database. It also creates needed vnfrs
+ :param rollback: list where this method appends created items at database in case a rollback may to be done
:param session: contains the used login username and working project
:param ns_request: params to be used for the nsr
:return: the _id of nsr descriptor stored at database
"""
- rollback = []
+ rollback_index = len(rollback)
step = ""
try:
# look for nsr
"orchestration-progress": {},
# {"networks": {"active": 0, "total": 0}, "vms": {"active": 0, "total": 0}},
- "crete-time": now,
+ "create-time": now,
"nsd-name-ref": nsd["name"],
"operational-events": [], # "id", "timestamp", "description", "event",
"nsd-ref": nsd["id"],
+ "nsdId": nsd["_id"],
"instantiate_params": ns_request,
"ns-instance-config-ref": nsr_id,
"id": nsr_id,
"created-time": now,
# "vnfd": vnfd, # at OSM model.but removed to avoid data duplication TODO: revise
"vnfd-ref": vnfd_id,
- "vnfd-id": vnfr_id, # not at OSM model, but useful
+ "vnfd-id": vnfd["_id"], # not at OSM model, but useful
"vim-account-id": None,
"vdur": [],
"connection-point": [],
vdur = {
"id": vdur_id,
"vdu-id-ref": vdu["id"],
+ # TODO "name": "" Name of the VDU in the VIM
"ip-address": None, # mgmt-interface filled by LCM
# "vim-id", "flavor-id", "image-id", "management-ip" # filled by LCM
"internal-connection-point": [],
+ "interfaces": [],
}
# TODO volumes: name, volume-id
for icp in vdu.get("internal-connection-point", ()):
# vim-id # TODO it would be nice having a vim port id
}
vdur["internal-connection-point"].append(vdu_icp)
+ for iface in vdu.get("interface", ()):
+ vdu_iface = {
+ "name": iface.get("name"),
+ # "ip-address", "mac-address" # filled by LCM
+ # vim-id # TODO it would be nice having a vim port id
+ }
+ vdur["interfaces"].append(vdu_iface)
vnfr_descriptor["vdur"].append(vdur)
step = "creating vnfr vnfd-id='{}' constituent-vnfd='{}' at database".format(
member_vnf["vnfd-id-ref"], member_vnf["member-vnf-index"])
self._format_new_data(session, "vnfrs", vnfr_descriptor)
self.db.create("vnfrs", vnfr_descriptor)
- rollback.append({"session": session, "item": "vnfrs", "_id": vnfr_id, "force": True})
+ rollback.insert(0, {"item": "vnfrs", "_id": vnfr_id})
nsr_descriptor["constituent-vnfr-ref"].append(vnfr_id)
step = "creating nsr at database"
self._format_new_data(session, "nsrs", nsr_descriptor)
self.db.create("nsrs", nsr_descriptor)
+ rollback.insert(rollback_index, {"item": "nsrs", "_id": nsr_id})
return nsr_id
except Exception as e:
raise EngineException("Error {}: {}".format(step, e))
- for rollback_item in rollback:
- try:
- self.engine.del_item(**rollback)
- except Exception as e2:
- self.logger.error("Rollback Exception {}: {}".format(rollback, e2))
@staticmethod
def _update_descriptor(desc, kwargs):
raise EngineException(
"Invalid query string '{}'. Index '{}' out of range".format(k, kitem_old))
- def new_item(self, session, item, indata={}, kwargs=None, headers={}, force=False):
+ def new_item(self, rollback, session, item, indata={}, kwargs=None, headers={}, force=False):
"""
Creates a new entry into database. For nsds and vnfds it creates an almost empty DISABLED entry,
that must be completed with a call to method upload_content
+ :param rollback: list where this method appends created items at database in case a rollback may to be done
:param session: contains the used login username and working project
:param item: it can be: users, projects, vim_accounts, sdns, nsrs, nsds, vnfds
:param indata: data to be inserted
:return: _id: identity of the inserted data.
"""
+ if not session["admin"] and item in ("users", "projects"):
+ raise EngineException("Needed admin privileges to perform this operation", HTTPStatus.UNAUTHORIZED)
+
try:
item_envelop = item
if item in ("nsds", "vnfds"):
# Override descriptor with query string kwargs
self._update_descriptor(content, kwargs)
- if not indata and item not in ("nsds", "vnfds"):
+ if not content and item not in ("nsds", "vnfds"):
raise EngineException("Empty payload")
validate_input(content, item, new=True)
if item == "nsrs":
# in this case the input descriptor is not the data to be stored
- return self.new_nsr(session, ns_request=content)
+ return self.new_nsr(rollback, session, ns_request=content)
- self._validate_new_data(session, item_envelop, content, force)
+ self._validate_new_data(session, item_envelop, content, force=force)
if item in ("nsds", "vnfds"):
content = {"_admin": {"userDefinedData": content}}
self._format_new_data(session, item, content)
_id = self.db.create(item, content)
+ rollback.insert(0, {"item": item, "_id": _id})
if item == "vim_accounts":
msg_data = self.db.get_one(item, {"_id": _id})
}
return nslcmop
- def ns_operation(self, session, nsInstanceId, operation, indata, kwargs=None):
+ def ns_operation(self, rollback, session, nsInstanceId, operation, indata, kwargs=None):
"""
Performs a new operation over a ns
+ :param rollback: list where this method appends created items at database in case a rollback may to be done
:param session: contains the used login username and working project
:param nsInstanceId: _id of the nsr to perform the operation
:param operation: it can be: instantiate, terminate, action, TODO: update, heal
nslcmop = self.new_nslcmop(session, nsInstanceId, operation, indata)
self._format_new_data(session, "nslcmops", nslcmop)
_id = self.db.create("nslcmops", nslcmop)
+ rollback.insert(0, {"item": "nslcmops", "_id": _id})
indata["_id"] = _id
self.msg.write("ns", operation, nslcmop)
return _id
# raise EngineException("Cannot get ns_instance '{}': {}".format(e), HTTPStatus.NOT_FOUND)
def _add_read_filter(self, session, item, filter):
- if session["project_id"] == "admin": # allows all
+ if session["admin"]: # allows all
return filter
if item == "users":
filter["username"] = session["username"]
- elif item in ("vnfds", "nsds", "nsrs"):
+ elif item in ("vnfds", "nsds", "nsrs", "vnfrs"):
filter["_admin.projects_read.cont"] = ["ANY", session["project_id"]]
def _add_delete_filter(self, session, item, filter):
- if session["project_id"] != "admin" and item in ("users", "projects"):
+ if not session["admin"] and item in ("users", "projects"):
raise EngineException("Only admin users can perform this task", http_code=HTTPStatus.FORBIDDEN)
if item == "users":
if filter.get("_id") == session["username"] or filter.get("username") == session["username"]:
elif item == "project":
if filter.get("_id") == session["project_id"]:
raise EngineException("You cannot delete your own project", http_code=HTTPStatus.CONFLICT)
- elif item in ("vnfds", "nsds") and session["project_id"] != "admin":
+ elif item in ("vnfds", "nsds") and not session["admin"]:
filter["_admin.projects_write.cont"] = ["ANY", session["project_id"]]
def get_file(self, session, item, _id, path=None, accept_header=None):
descriptor = self.get_item(session, item, _id)
descriptor_id = descriptor.get("id")
if descriptor_id:
- self._check_dependencies_on_descriptor(session, item, descriptor_id)
+ self._check_dependencies_on_descriptor(session, item, descriptor_id, _id)
+ elif item == "projects":
+ if not force:
+ self._check_project_dependencies(_id)
if item == "nsrs":
nsr = self.db.get_one(item, filter)
except ValidationError as e:
raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
- _deep_update(content, indata)
- self._validate_new_data(session, item, content, id, force)
+ self._check_edition(session, item, indata, id, force)
+ deep_update(content, indata)
+ self._validate_new_data(session, item, content, id=id, force=force)
# self._format_new_data(session, item, content)
self.db.replace(item, id, content)
if item in ("vim_accounts", "sdns"):
:param force: If True avoid some dependence checks
:return: dictionary, raise exception if not found.
"""
+ if not session["admin"] and item == "projects":
+ raise EngineException("Needed admin privileges to perform this operation", HTTPStatus.UNAUTHORIZED)
content = self.get_item(session, item, _id)
return self._edit_item(session, item, _id, content, indata, kwargs, force)