import yaml
import json
import logging
-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 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_var):
"""
- 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_var is None it just returns an empty tuple
+ :param input_var: can be a list, tuple or None
+ :return: input_var or () if it is None
"""
- 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_var is None:
+ return ()
+ return input_var
class Engine(object):
except (DbException, FsException, MsgException) as e:
raise EngineException(str(e), http_code=e.http_code)
- def authorize(self, token):
- try:
- if not token:
- raise EngineException("Needed a token or Authorization http header",
- http_code=HTTPStatus.UNAUTHORIZED)
- if token not in self.tokens:
- raise EngineException("Invalid token or Authorization http header",
- http_code=HTTPStatus.UNAUTHORIZED)
- session = self.tokens[token]
- now = time()
- if session["expires"] < now:
- del self.tokens[token]
- raise EngineException("Expired Token or Authorization http header",
- http_code=HTTPStatus.UNAUTHORIZED)
- return session
- except EngineException:
- if self.config["global"].get("test.user_not_authorized"):
- return {"id": "fake-token-id-for-test",
- "project_id": self.config["global"].get("test.project_not_authorized", "admin"),
- "username": self.config["global"]["test.user_not_authorized"]}
- else:
- raise
-
- def new_token(self, session, indata, remote):
- now = time()
- user_content = None
-
- # Try using username/password
- if indata.get("username"):
- user_rows = self.db.get_list("users", {"username": indata.get("username")})
- user_content = None
- if user_rows:
- user_content = user_rows[0]
- salt = user_content["_admin"]["salt"]
- shadow_password = sha256(indata.get("password", "").encode('utf-8') + salt.encode('utf-8')).hexdigest()
- if shadow_password != user_content["password"]:
- user_content = None
- if not user_content:
- raise EngineException("Invalid username/password", http_code=HTTPStatus.UNAUTHORIZED)
- elif session:
- user_rows = self.db.get_list("users", {"username": session["username"]})
- if user_rows:
- user_content = user_rows[0]
- else:
- raise EngineException("Invalid token", http_code=HTTPStatus.UNAUTHORIZED)
- else:
- raise EngineException("Provide credentials: username/password or Authorization Bearer token",
- http_code=HTTPStatus.UNAUTHORIZED)
-
- token_id = ''.join(random_choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
- for _ in range(0, 32))
- if indata.get("project_id"):
- project_id = indata.get("project_id")
- if project_id not in user_content["projects"]:
- raise EngineException("project {} not allowed for this user".format(project_id),
- http_code=HTTPStatus.UNAUTHORIZED)
- else:
- project_id = user_content["projects"][0]
- if project_id == "admin":
- session_admin = True
- else:
- project = self.db.get_one("projects", {"_id": project_id})
- session_admin = project.get("admin", False)
- new_session = {"issued_at": now, "expires": now+3600,
- "_id": token_id, "id": token_id, "project_id": project_id, "username": user_content["username"],
- "remote_port": remote.port, "admin": session_admin}
- if remote.name:
- new_session["remote_host"] = remote.name
- elif remote.ip:
- new_session["remote_host"] = remote.ip
-
- self.tokens[token_id] = new_session
- return deepcopy(new_session)
-
- def get_token_list(self, session):
- token_list = []
- for token_id, token_value in self.tokens.items():
- if token_value["username"] == session["username"]:
- token_list.append(deepcopy(token_value))
- return token_list
-
- def get_token(self, session, token_id):
- token_value = self.tokens.get(token_id)
- if not token_value:
- raise EngineException("token not found", http_code=HTTPStatus.NOT_FOUND)
- if token_value["username"] != session["username"] and not session["admin"]:
- raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED)
- return token_value
-
- def del_token(self, token_id):
- try:
- del self.tokens[token_id]
- return "token '{}' deleted".format(token_id)
- except KeyError:
- raise EngineException("Token '{}' not found".format(token_id), http_code=HTTPStatus.NOT_FOUND)
-
@staticmethod
def _remove_envelop(item, indata=None):
"""
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 project_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_pdus_usage(self, pdu_id):
+ """
+ Check if a pdu can be deleted
+ :param pdu_id:
+ :return:
+ """
+ # TODO Is it needed to check descriptors _admin.project_read/project_write??
+ _filter = {"vdur.pdu-id": pdu_id}
+ if self.db.get_list("vnfrs", _filter):
+ raise EngineException("There are ns that uses this pdu", 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()
else:
if not indata.get("_id"):
indata["_id"] = str(uuid4())
- if item in ("vnfds", "nsds", "nsrs", "vnfrs"):
+ if item in ("vnfds", "nsds", "nsrs", "vnfrs", "pdus"):
if not indata["_admin"].get("projects_read"):
indata["_admin"]["projects_read"] = [session["project_id"]]
if not indata["_admin"].get("projects_write"):
indata["_admin"]["projects_write"] = [session["project_id"]]
- if item in ("vnfds", "nsds"):
+ if item in ("vnfds", "nsds", "pdus"):
indata["_admin"]["onboardingState"] = "CREATED"
indata["_admin"]["operationalState"] = "DISABLED"
indata["_admin"]["usageSate"] = "NOT_IN_USE"
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": [],
}
+ if vnfd.get("pdu-type"):
+ vdur["pdu-type"] = vnfd["pdu-type"]
# TODO volumes: name, volume-id
for icp in vdu.get("internal-connection-point", ()):
vdu_icp = {
# 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", "pdus"):
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", "pdus") 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)
+ elif item == "pdus":
+ if not force:
+ self._check_pdus_usage(_id)
if item == "nsrs":
nsr = self.db.get_one(item, filter)
v = self.db.del_one(item, {"_id": _id})
self.db.del_list("nslcmops", {"nsInstanceId": _id})
self.db.del_list("vnfrs", {"nsr-id-ref": _id})
+ # set all used pdus as free
+ self.db.set_list("pdus", {"_admin.usage.nsr_id": _id},
+ {"_admin.usageSate": "NOT_IN_USE", "_admin.usage": None})
self.msg.write("ns", "deleted", {"_id": _id})
return v
if item in ("vim_accounts", "sdns") and not force:
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)