Merge remote-tracking branch 'origin/master' into v9.0
Change-Id: Ib429d0fbc6ecfeff1b29066683f71b1594cf5a46
Signed-off-by: garciadeblas <gerardo.garciadeblas@telefonica.com>
diff --git a/.gitignore-common b/.gitignore-common
index c65d2a2..771afd0 100644
--- a/.gitignore-common
+++ b/.gitignore-common
@@ -32,6 +32,9 @@
.pydevproject
.settings
+#vscode
+.vscode
+
#local stuff files that end in ".local" or folders called "local"
local
osm_nbi/local
diff --git a/Dockerfile.local b/Dockerfile.local
index 5ba1240..0794a27 100644
--- a/Dockerfile.local
+++ b/Dockerfile.local
@@ -106,5 +106,5 @@
ADD . /app/NBI
# Run app.py when the container launches
-CMD python3 -m osm_nbi.nbi
+CMD ["python3", "-m", "osm_nbi.nbi"]
diff --git a/osm_nbi/admin_topics.py b/osm_nbi/admin_topics.py
index 4f9ab0c..819ec42 100644
--- a/osm_nbi/admin_topics.py
+++ b/osm_nbi/admin_topics.py
@@ -243,6 +243,8 @@
if not session["force"] and edit_content.get("name"):
self.check_unique_name(session, edit_content["name"], _id=_id)
+ return final_content
+
def format_on_edit(self, final_content, edit_content):
"""
Modifies final_content inserting admin information upon edition
@@ -459,8 +461,8 @@
return oid
def check_conflict_on_edit(self, session, final_content, edit_content, _id):
- super(CommonVimWimSdn, self).check_conflict_on_edit(session, final_content, edit_content, _id)
- super().check_conflict_on_edit(session, final_content, edit_content, _id)
+ final_content = super(CommonVimWimSdn, self).check_conflict_on_edit(session, final_content, edit_content, _id)
+ final_content = super().check_conflict_on_edit(session, final_content, edit_content, _id)
# Update Helm/Juju Repo lists
repos = {"helm-chart": [], "juju-bundle": []}
for proj in session.get("set_project", []):
@@ -473,6 +475,7 @@
if rlist not in final_content["_admin"]:
final_content["_admin"][rlist] = []
final_content["_admin"][rlist] += repos[k]
+ return final_content
def check_conflict_on_del(self, session, _id, db_content):
"""
@@ -605,6 +608,8 @@
raise EngineException("You cannot remove system_admin role from admin user",
http_code=HTTPStatus.FORBIDDEN)
+ return final_content
+
def check_conflict_on_del(self, session, _id, db_content):
"""
Check if deletion can be done because of dependencies if it is not force. To override
@@ -720,7 +725,7 @@
if not content:
content = self.show(session, _id)
indata = self._validate_input_edit(indata, content, force=session["force"])
- self.check_conflict_on_edit(session, content, indata, _id=_id)
+ content = self.check_conflict_on_edit(session, content, indata, _id=_id)
# self.format_on_edit(content, indata)
if not ("password" in indata or "username" in indata or indata.get("remove_project_role_mappings") or
@@ -903,6 +908,7 @@
# Check that project name is not used, regardless keystone already checks this
if project_name and self.auth.get_project_list(filter_q={"name": project_name}):
raise EngineException("project '{}' is already used".format(project_name), HTTPStatus.CONFLICT)
+ return final_content
def check_conflict_on_del(self, session, _id, db_content):
"""
@@ -1045,7 +1051,7 @@
if not content:
content = self.show(session, _id)
indata = self._validate_input_edit(indata, content, force=session["force"])
- self.check_conflict_on_edit(session, content, indata, _id=_id)
+ content = self.check_conflict_on_edit(session, content, indata, _id=_id)
self.format_on_edit(content, indata)
content_original = copy.deepcopy(content)
deep_update_rfc7396(content, indata)
@@ -1174,6 +1180,8 @@
if roles and roles[0][BaseTopic.id_field("roles", _id)] != _id:
raise EngineException("role name '{}' exists".format(role_name), HTTPStatus.CONFLICT)
+ return final_content
+
def check_conflict_on_del(self, session, _id, db_content):
"""
Check if deletion can be done because of dependencies if it is not force. To override
@@ -1348,7 +1356,7 @@
content = self.show(session, _id)
indata = self._validate_input_edit(indata, content, force=session["force"])
deep_update_rfc7396(content, indata)
- self.check_conflict_on_edit(session, content, indata, _id=_id)
+ content = self.check_conflict_on_edit(session, content, indata, _id=_id)
self.format_on_edit(content, indata)
self.auth.update_role(content)
except ValidationError as e:
diff --git a/osm_nbi/base_topic.py b/osm_nbi/base_topic.py
index bbe6337..f597d17 100644
--- a/osm_nbi/base_topic.py
+++ b/osm_nbi/base_topic.py
@@ -231,10 +231,10 @@
:param final_content: data once modified. This method may change it.
:param edit_content: incremental data that contains the modifications to apply
:param _id: internal _id
- :return: None or raises EngineException
+ :return: final_content or raises EngineException
"""
if not self.multiproject:
- return
+ return final_content
# Change public status
if session["public"] is not None:
if session["public"] and "ANY" not in final_content["_admin"]["projects_read"]:
@@ -249,6 +249,8 @@
if p not in final_content["_admin"]["projects_read"]:
final_content["_admin"]["projects_read"].append(p)
+ return final_content
+
def check_unique_name(self, session, name, _id=None):
"""
Check that the name is unique for this project
@@ -578,19 +580,16 @@
if indata and session.get("set_project"):
raise EngineException("Cannot edit content and set to project (query string SET_PROJECT) at same time",
HTTPStatus.UNPROCESSABLE_ENTITY)
-
# TODO self._check_edition(session, indata, _id, force)
if not content:
content = self.show(session, _id)
-
indata = self._validate_input_edit(indata, content, force=session["force"])
-
deep_update_rfc7396(content, indata)
# To allow project addressing by name AS WELL AS _id. Get the _id, just in case the provided one is a name
_id = content.get("_id") or _id
- self.check_conflict_on_edit(session, content, indata, _id=_id)
+ content = self.check_conflict_on_edit(session, content, indata, _id=_id)
op_id = self.format_on_edit(content, indata)
self.db.replace(self.topic, _id, content)
diff --git a/osm_nbi/descriptor_topics.py b/osm_nbi/descriptor_topics.py
index ed958b1..0ad3e83 100644
--- a/osm_nbi/descriptor_topics.py
+++ b/osm_nbi/descriptor_topics.py
@@ -17,6 +17,7 @@
import yaml
import json
import importlib
+import copy
# import logging
from hashlib import md5
from osm_common.dbbase import DbException, deep_update_rfc7396
@@ -32,6 +33,7 @@
from osm_im.nst import nst as nst_im
from pyangbind.lib.serialise import pybindJSONDecoder
import pyangbind.lib.pybindJSON as pybindJSON
+from osm_nbi import utils
__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
@@ -42,7 +44,7 @@
BaseTopic.__init__(self, db, fs, msg, auth)
def check_conflict_on_edit(self, session, final_content, edit_content, _id):
- super().check_conflict_on_edit(session, final_content, edit_content, _id)
+ final_content = super().check_conflict_on_edit(session, final_content, edit_content, _id)
def _check_unique_id_name(descriptor, position=""):
for desc_key, desc_item in descriptor.items():
@@ -73,24 +75,29 @@
internal_keys[k] = final_content.pop(k)
storage_params = internal_keys["_admin"].get("storage")
serialized = self._validate_input_new(final_content, storage_params, session["force"])
+
# 1.2. modify final_content with a serialized version
- final_content.clear()
- final_content.update(serialized)
+ final_content = copy.deepcopy(serialized)
# 1.3. restore internal keys
for k, v in internal_keys.items():
final_content[k] = v
if session["force"]:
- return
+ return final_content
+
# 2. check that this id is not present
if "id" in edit_content:
_filter = self._get_project_filter(session)
+
_filter["id"] = final_content["id"]
_filter["_id.neq"] = _id
+
if self.db.get_one(self.topic, _filter, fail_on_empty=False):
raise EngineException("{} with id '{}' already exists for this project".format(self.topic[:-1],
final_content["id"]),
HTTPStatus.CONFLICT)
+ return final_content
+
@staticmethod
def format_on_new(content, project_id=None, make_public=False):
BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
@@ -303,7 +310,7 @@
self._update_input_with_kwargs(indata, kwargs)
deep_update_rfc7396(current_desc, indata)
- self.check_conflict_on_edit(session, current_desc, indata, _id=_id)
+ current_desc = self.check_conflict_on_edit(session, current_desc, indata, _id=_id)
current_desc["_admin"]["modified"] = time()
self.db.replace(self.topic, _id, current_desc)
self.fs.dir_rename(temp_folder, _id)
@@ -460,19 +467,13 @@
raise EngineException("ERROR: Unsupported descriptor format. Please, use an ETSI SOL006 descriptor.",
http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
try:
- virtual_compute_descriptors = data.get('virtual-compute-desc')
- virtual_storage_descriptors = data.get('virtual-storage-desc')
myvnfd = etsi_nfv_vnfd.etsi_nfv_vnfd()
pybindJSONDecoder.load_ietf_json({'etsi-nfv-vnfd:vnfd': data}, None, None, obj=myvnfd,
path_helper=True, skip_unknown=force)
out = pybindJSON.dumps(myvnfd, mode="ietf")
desc_out = self._remove_envelop(yaml.safe_load(out))
desc_out = self._remove_yang_prefixes_from_descriptor(desc_out)
- if virtual_compute_descriptors:
- desc_out['virtual-compute-desc'] = virtual_compute_descriptors
- if virtual_storage_descriptors:
- desc_out['virtual-storage-desc'] = virtual_storage_descriptors
- return desc_out
+ return utils.deep_update_dict(data, desc_out)
except Exception as e:
raise EngineException("Error in pyangbind validation: {}".format(str(e)),
http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
@@ -499,7 +500,7 @@
return clean_indata
def check_conflict_on_edit(self, session, final_content, edit_content, _id):
- super().check_conflict_on_edit(session, final_content, edit_content, _id)
+ final_content = super().check_conflict_on_edit(session, final_content, edit_content, _id)
# set type of vnfd
contains_pdu = False
@@ -514,6 +515,7 @@
elif contains_vdu:
final_content["_admin"]["type"] = "vnfd"
# if neither vud nor pdu do not fill type
+ return final_content
def check_conflict_on_del(self, session, _id, db_content):
"""
@@ -559,8 +561,8 @@
for vdu in get_iterable(indata.get("vdu")):
self.validate_vdu_internal_connection_points(vdu)
- self._validate_vdu_charms_in_package(storage_params, vdu, indata)
self._validate_vdu_cloud_init_in_package(storage_params, vdu, indata)
+ self._validate_vdu_charms_in_package(storage_params, indata)
self._validate_vnf_charms_in_package(storage_params, indata)
@@ -619,14 +621,20 @@
http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
# TODO: Validate k8s-cluster-net points to a valid k8s-cluster:nets ?
- def _validate_vdu_charms_in_package(self, storage_params, vdu, indata):
- if not vdu.get("vdu-configuration"):
- return
- for vdu_configuration in get_iterable(indata.get("vdu-configuration")):
- if vdu_configuration.get("juju"):
- if not self._validate_package_folders(storage_params, 'charms'):
- raise EngineException("Charm defined in vnf[id={}] but not present in "
- "package".format(indata["id"]))
+ def _validate_vdu_charms_in_package(self, storage_params, indata):
+ for df in indata["df"]:
+ if "lcm-operations-configuration" in df and "operate-vnf-op-config" in df["lcm-operations-configuration"]:
+ configs = df["lcm-operations-configuration"]["operate-vnf-op-config"].get("day1-2", [])
+ vdus = df["vdu-profile"]
+ for vdu in vdus:
+ for config in configs:
+ if config["id"] == vdu["id"] and utils.find_in_list(
+ config.get("execution-environment-list", []),
+ lambda ee: "juju" in ee
+ ):
+ if not self._validate_package_folders(storage_params, 'charms'):
+ raise EngineException("Charm defined in vnf[id={}] but not present in "
+ "package".format(indata["id"]))
def _validate_vdu_cloud_init_in_package(self, storage_params, vdu, indata):
if not vdu.get("cloud-init-file"):
@@ -636,13 +644,21 @@
"package".format(indata["id"], vdu["id"]))
def _validate_vnf_charms_in_package(self, storage_params, indata):
- if not indata.get("vnf-configuration"):
- return
- for vnf_configuration in get_iterable(indata.get("vnf-configuration")):
- if vnf_configuration.get("juju"):
- if not self._validate_package_folders(storage_params, 'charms'):
- raise EngineException("Charm defined in vnf[id={}] but not present in "
- "package".format(indata["id"]))
+ # Get VNF configuration through new container
+ for deployment_flavor in indata.get('df', []):
+ if "lcm-operations-configuration" not in deployment_flavor:
+ return
+ if "operate-vnf-op-config" not in deployment_flavor["lcm-operations-configuration"]:
+ return
+ for day_1_2_config in deployment_flavor["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"]:
+ if day_1_2_config["id"] == indata["id"]:
+ if utils.find_in_list(
+ day_1_2_config.get("execution-environment-list", []),
+ lambda ee: "juju" in ee
+ ):
+ if not self._validate_package_folders(storage_params, 'charms'):
+ raise EngineException("Charm defined in vnf[id={}] but not present in "
+ "package".format(indata["id"]))
def _validate_package_folders(self, storage_params, folder, file=None):
if not storage_params or not storage_params.get("pkg-dir"):
@@ -753,19 +769,25 @@
http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
for sca in get_iterable(sa.get("scaling-config-action")):
- if not indata.get("vnf-configuration"):
- raise EngineException("'vnf-configuration' not defined in the descriptor but it is referenced "
- "by df[id='{}']:scaling-aspect[id='{}']:scaling-config-action"
+ if "lcm-operations-configuration" not in df \
+ or "operate-vnf-op-config" not in df["lcm-operations-configuration"] \
+ or not utils.find_in_list(
+ df["lcm-operations-configuration"]["operate-vnf-op-config"].get("day1-2", []),
+ lambda config: config["id"] == indata["id"]):
+ raise EngineException("'day1-2 configuration' not defined in the descriptor but it is "
+ "referenced by df[id='{}']:scaling-aspect[id='{}']:scaling-config-action"
.format(df["id"], sa["id"]),
http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
- for configuration in get_iterable(indata["vnf-configuration"]):
+ for configuration in get_iterable(
+ df["lcm-operations-configuration"]["operate-vnf-op-config"].get("day1-2", [])
+ ):
for primitive in get_iterable(configuration.get("config-primitive")):
if primitive["name"] == sca["vnf-config-primitive-name-ref"]:
break
else:
raise EngineException("df[id='{}']:scaling-aspect[id='{}']:scaling-config-action:vnf-"
"config-primitive-name-ref='{}' does not match any "
- "vnf-configuration:config-primitive:name"
+ "day1-2 configuration:config-primitive:name"
.format(df["id"], sa["id"], sca["vnf-config-primitive-name-ref"]),
http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
@@ -971,10 +993,12 @@
http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
def check_conflict_on_edit(self, session, final_content, edit_content, _id):
- super().check_conflict_on_edit(session, final_content, edit_content, _id)
+ final_content = super().check_conflict_on_edit(session, final_content, edit_content, _id)
self._check_descriptor_dependencies(session, final_content)
+ return final_content
+
def check_conflict_on_del(self, session, _id, db_content):
"""
Check that there is not any NSR that uses this NSD. Only NSRs belonging to this project are considered. Note
@@ -1078,9 +1102,10 @@
"existing nsd".format(nsd_id), http_code=HTTPStatus.CONFLICT)
def check_conflict_on_edit(self, session, final_content, edit_content, _id):
- super().check_conflict_on_edit(session, final_content, edit_content, _id)
+ final_content = super().check_conflict_on_edit(session, final_content, edit_content, _id)
self._check_descriptor_dependencies(session, final_content)
+ return final_content
def check_conflict_on_del(self, session, _id, db_content):
"""
diff --git a/osm_nbi/instance_topics.py b/osm_nbi/instance_topics.py
index 35c39d3..5088b24 100644
--- a/osm_nbi/instance_topics.py
+++ b/osm_nbi/instance_topics.py
@@ -177,33 +177,29 @@
additional_params[k] = "!!yaml " + safe_dump(v)
if descriptor:
- # check that enough parameters are supplied for the initial-config-primitive
- # TODO: check for cloud-init
- if member_vnf_index:
- if kdu_name:
- initial_primitives = None
- elif vdu_id:
- vdud = next(x for x in descriptor["vdu"] if x["id"] == vdu_id)
- initial_primitives = deep_get(vdud, ("vdu-configuration", "initial-config-primitive"))
- else:
- vnf_configurations = get_iterable(descriptor.get("vnf-configuration"))
+ for df in descriptor.get("df", []):
+ # check that enough parameters are supplied for the initial-config-primitive
+ # TODO: check for cloud-init
+ if member_vnf_index:
initial_primitives = []
- for vnfc in vnf_configurations:
- for primitive in get_iterable(vnfc.get("initial-config-primitive")):
- initial_primitives.append(primitive)
- else:
- initial_primitives = deep_get(descriptor, ("ns-configuration", "initial-config-primitive"))
+ if "lcm-operations-configuration" in df \
+ and "operate-vnf-op-config" in df["lcm-operations-configuration"]:
+ for config in df["lcm-operations-configuration"]["operate-vnf-op-config"].get("day1-2", []):
+ for primitive in get_iterable(config.get("initial-config-primitive")):
+ initial_primitives.append(primitive)
+ else:
+ initial_primitives = deep_get(descriptor, ("ns-configuration", "initial-config-primitive"))
- for initial_primitive in get_iterable(initial_primitives):
- for param in get_iterable(initial_primitive.get("parameter")):
- if param["value"].startswith("<") and param["value"].endswith(">"):
- if param["value"] in ("<rw_mgmt_ip>", "<VDU_SCALE_INFO>", "<ns_config_info>"):
- continue
- if not additional_params or param["value"][1:-1] not in additional_params:
- raise EngineException("Parameter '{}' needed for vnfd[id={}]:vnf-configuration:"
- "initial-config-primitive[name={}] not supplied".
- format(param["value"], descriptor["id"],
- initial_primitive["name"]))
+ for initial_primitive in get_iterable(initial_primitives):
+ for param in get_iterable(initial_primitive.get("parameter")):
+ if param["value"].startswith("<") and param["value"].endswith(">"):
+ if param["value"] in ("<rw_mgmt_ip>", "<VDU_SCALE_INFO>", "<ns_config_info>"):
+ continue
+ if not additional_params or param["value"][1:-1] not in additional_params:
+ raise EngineException("Parameter '{}' needed for vnfd[id={}]:day1-2 configuration:"
+ "initial-config-primitive[name={}] not supplied".
+ format(param["value"], descriptor["id"],
+ initial_primitive["name"]))
return additional_params or None, other_params or None
@@ -226,7 +222,7 @@
step = "validating input parameters"
ns_request = self._remove_envelop(indata)
self._update_input_with_kwargs(ns_request, kwargs)
- self._validate_input_new(ns_request, session["force"])
+ ns_request = self._validate_input_new(ns_request, session["force"])
step = "getting nsd id='{}' from database".format(ns_request.get("nsdId"))
nsd = self._get_nsd_from_db(ns_request["nsdId"], session)
@@ -423,18 +419,13 @@
sw_image_id = vdu.get("sw-image-desc")
if sw_image_id:
- sw_image_desc = utils.find_in_list(vnfd.get("sw-image-desc", ()),
- lambda sw: sw["id"] == sw_image_id)
- image_data = {}
- if sw_image_desc.get("image"):
- image_data["image"] = sw_image_desc["image"]
- if sw_image_desc.get("checksum"):
- image_data["image_checksum"] = sw_image_desc["checksum"]["hash"]
- img = next((f for f in nsr_descriptor["image"] if
- all(f.get(k) == image_data[k] for k in image_data)), None)
- if not img:
- image_data["id"] = str(len(nsr_descriptor["image"]))
- nsr_descriptor["image"].append(image_data)
+ image_data = self._get_image_data_from_vnfd(vnfd, sw_image_id)
+ self._add_image_to_nsr(nsr_descriptor, image_data)
+
+ # also add alternative images to the list of images
+ for alt_image in vdu.get("alternative-sw-image-desc", ()):
+ image_data = self._get_image_data_from_vnfd(vnfd, alt_image)
+ self._add_image_to_nsr(nsr_descriptor, image_data)
for vld in nsr_vld:
vld["vnfd-connection-point-ref"] = all_vld_connection_point_data.get(vld.get("id"), [])
@@ -443,6 +434,28 @@
return nsr_descriptor
+ def _get_image_data_from_vnfd(self, vnfd, sw_image_id):
+ sw_image_desc = utils.find_in_list(vnfd.get("sw-image-desc", ()),
+ lambda sw: sw["id"] == sw_image_id)
+ image_data = {}
+ if sw_image_desc.get("image"):
+ image_data["image"] = sw_image_desc["image"]
+ if sw_image_desc.get("checksum"):
+ image_data["image_checksum"] = sw_image_desc["checksum"]["hash"]
+ if sw_image_desc.get("vim-type"):
+ image_data["vim-type"] = sw_image_desc["vim-type"]
+ return image_data
+
+ def _add_image_to_nsr(self, nsr_descriptor, image_data):
+ """
+ Adds image to nsr checking first it is not already added
+ """
+ img = next((f for f in nsr_descriptor["image"] if
+ all(f.get(k) == image_data[k] for k in image_data)), None)
+ if not img:
+ image_data["id"] = str(len(nsr_descriptor["image"]))
+ nsr_descriptor["image"].append(image_data)
+
def _create_vnfr_descriptor_from_vnfd(self, nsd, vnfd, vnfd_id, vnf_index, nsr_descriptor,
ns_request, ns_k8s_namespace):
vnfr_id = str(uuid4())
@@ -532,7 +545,23 @@
vnfr_descriptor["kdur"].append(kdur)
vnfd_mgmt_cp = vnfd.get("mgmt-cp")
+
for vdu in vnfd.get("vdu", ()):
+ vdu_mgmt_cp = []
+ try:
+ configs = vnfd.get("df")[0]["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"]
+ vdu_config = utils.find_in_list(configs, lambda config: config["id"] == vdu["id"])
+ except Exception:
+ vdu_config = None
+
+ if vdu_config:
+ external_connection_ee = utils.filter_in_list(
+ vdu_config.get("execution-environment-list", []),
+ lambda ee: "external-connection-point-ref" in ee
+ )
+ for ee in external_connection_ee:
+ vdu_mgmt_cp.append(ee["external-connection-point-ref"])
+
additional_params, vdu_params = self._format_additional_params(
ns_request, vnf_index, vdu_id=vdu["id"], descriptor=vnfd)
vdur = {
@@ -559,6 +588,13 @@
"connection-point-id": icp["id"],
"name": icp.get("id"),
}
+
+ if "port-security-enabled" in icp:
+ vdu_icp["port-security-enabled"] = icp["port-security-enabled"]
+
+ if "port-security-disable-strategy" in icp:
+ vdu_icp["port-security-disable-strategy"] = icp["port-security-disable-strategy"]
+
vdur["internal-connection-point"].append(vdu_icp)
for iface in icp.get("virtual-network-interface-requirement", ()):
@@ -577,7 +613,11 @@
if vnfd_mgmt_cp and vdu_iface.get("external-connection-point-ref") == vnfd_mgmt_cp:
vdu_iface["mgmt-vnf"] = True
- vdu_iface["mgmt-interface"] = True # TODO change to mgmt-vdu
+ vdu_iface["mgmt-interface"] = True
+
+ for ecp in vdu_mgmt_cp:
+ if vdu_iface.get("external-connection-point-ref") == ecp:
+ vdu_iface["mgmt-interface"] = True
if iface.get("virtual-interface"):
vdu_iface.update(deepcopy(iface["virtual-interface"]))
@@ -588,13 +628,25 @@
# TODO: Change for multiple df support
for df in get_iterable(nsd.get("df")):
for vnf_profile in get_iterable(df.get("vnf-profile")):
- for vlc in get_iterable(vnf_profile.get("virtual-link-connectivity")):
+ for vlc_index, vlc in \
+ enumerate(get_iterable(vnf_profile.get("virtual-link-connectivity"))):
for cpd in get_iterable(vlc.get("constituent-cpd-id")):
if cpd.get("constituent-cpd-id") == iface_ext_cp:
vdu_iface["ns-vld-id"] = vlc.get("virtual-link-profile-id")
+ # if iface type is SRIOV or PASSTHROUGH, set pci-interfaces flag to True
+ if vdu_iface.get("type") in ("SR-IOV", "PCI-PASSTHROUGH"):
+ nsr_descriptor["vld"][vlc_index]["pci-interfaces"] = True
break
elif vdu_iface.get("internal-connection-point-ref"):
vdu_iface["vnf-vld-id"] = icp.get("int-virtual-link-desc")
+ # TODO: store fixed IP address in the record (if it exists in the ICP)
+ # if iface type is SRIOV or PASSTHROUGH, set pci-interfaces flag to True
+ if vdu_iface.get("type") in ("SR-IOV", "PCI-PASSTHROUGH"):
+ ivld_index = utils.find_index_in_list(vnfd.get("int-virtual-link-desc", ()),
+ lambda ivld:
+ ivld["id"] == icp.get("int-virtual-link-desc")
+ )
+ vnfr_descriptor["vld"][ivld_index]["pci-interfaces"] = True
vdur["interfaces"].append(vdu_iface)
@@ -608,6 +660,19 @@
)
vdur["ns-image-id"] = nsr_sw_image_data["id"]
+ if vdu.get("alternative-sw-image-desc"):
+ alt_image_ids = []
+ for alt_image_id in vdu.get("alternative-sw-image-desc", ()):
+ sw_image = utils.find_in_list(
+ vnfd.get("sw-image-desc", ()),
+ lambda image: image["id"] == alt_image_id)
+ nsr_sw_image_data = utils.find_in_list(
+ nsr_descriptor["image"],
+ lambda nsr_image: (nsr_image.get("image") == sw_image.get("image"))
+ )
+ alt_image_ids.append(nsr_sw_image_data["id"])
+ vdur["alt-image-ids"] = alt_image_ids
+
flavor_data_name = vdu["id"][:56] + "-flv"
nsr_flavor_desc = utils.find_in_list(
nsr_descriptor["flavor"],
@@ -689,18 +754,28 @@
indata["member_vnf_index"] = indata.pop("vnf_member_index") # for backward compatibility
if indata.get("member_vnf_index"):
vnfd = self._get_vnfd_from_vnf_member_index(indata["member_vnf_index"], nsr["_id"])
+ try:
+ configs = vnfd.get("df")[0]["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"]
+ except Exception:
+ configs = []
+
if indata.get("vdu_id"):
self._check_valid_vdu(vnfd, indata["vdu_id"])
- # TODO: Change the [0] as vdu-configuration is now a list
- descriptor_configuration = vnfd.get("vdu-configuration", [{}])[0].get("config-primitive")
+ descriptor_configuration = utils.find_in_list(
+ configs,
+ lambda config: config["id"] == indata["vdu_id"]
+ ).get("config-primitive")
elif indata.get("kdu_name"):
self._check_valid_kdu(vnfd, indata["kdu_name"])
- # TODO: Change the [0] as kdu-configuration is now a list
- kdud = next((k for k in vnfd["kdu"] if k["name"] == indata["kdu_name"]), None)
- descriptor_configuration = deep_get(kdud, ("kdu-configuration", "config-primitive"))
+ descriptor_configuration = utils.find_in_list(
+ configs,
+ lambda config: config["id"] == indata.get("kdu_name")
+ ).get("config-primitive")
else:
- # TODO: Change the [0] as vnf-configuration is now a list
- descriptor_configuration = vnfd.get("vnf-configuration", [{}])[0].get("config-primitive")
+ descriptor_configuration = utils.find_in_list(
+ configs,
+ lambda config: config["id"] == vnfd["id"]
+ ).get("config-primitive")
else: # use a NSD
descriptor_configuration = nsd.get("ns-configuration", {}).get("config-primitive")
@@ -1098,6 +1173,8 @@
vnfr_update[vnfr_update_text + ".mac-address"] = increment_ip_mac(
iface_inst_param.get("mac-address"), vdur.get("count-index", 0))
vnfr_update[vnfr_update_text + ".fixed-mac"] = True
+ if iface_inst_param.get("floating-ip-required"):
+ vnfr_update[vnfr_update_text + ".floating-ip-required"] = True
# get vnf.internal-vld.internal-conection-point instantiation params to update vnfr.vdur.interfaces
# TODO update vld with the ip-profile
for ivld_inst_param in get_iterable(vnf_inst_params.get("internal-vld")):
@@ -1418,7 +1495,7 @@
slice_request = self._remove_envelop(indata)
# Override descriptor with query string kwargs
self._update_input_with_kwargs(slice_request, kwargs)
- self._validate_input_new(slice_request, session["force"])
+ slice_request = self._validate_input_new(slice_request, session["force"])
# look for nstd
step = "getting nstd id='{}' from database".format(slice_request.get("nstId"))
diff --git a/osm_nbi/pmjobs_topics.py b/osm_nbi/pmjobs_topics.py
index 6ce8b3d..3142744 100644
--- a/osm_nbi/pmjobs_topics.py
+++ b/osm_nbi/pmjobs_topics.py
@@ -38,15 +38,14 @@
else:
for vnfr in vnfr_desc:
vnfd_desc = self.db.get_one("vnfds", {"_id": vnfr["vnfd-id"]}, fail_on_empty=True, fail_on_more=False)
- if vnfd_desc.get("vdu"):
- for vdu in vnfd_desc['vdu']:
- # Checks for vdu metric in vdu-configuration
- if 'vdu-configuration' in vdu and 'metrics' in vdu['vdu-configuration']:
- metric_list.extend([quote(metric['name'])
- for metric in vdu["vdu-configuration"]["metrics"]])
- # Checks for vnf metric in vnf-configutaion
- if 'vnf-configuration' in vnfd_desc and 'metrics' in vnfd_desc['vnf-configuration']:
- metric_list.extend([quote(metric['name']) for metric in vnfd_desc["vnf-configuration"]["metrics"]])
+ try:
+ configs = vnfd_desc.get("df")[0]["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"]
+ except Exception:
+ configs = []
+
+ for config in configs:
+ if "metrics" in config:
+ metric_list.extend([quote(metric['name']) for metric in config["metrics"]])
metric_list = list(set(metric_list))
return metric_list
diff --git a/osm_nbi/tests/run_test.py b/osm_nbi/tests/run_test.py
index 3033a34..9e85358 100755
--- a/osm_nbi/tests/run_test.py
+++ b/osm_nbi/tests/run_test.py
@@ -3195,7 +3195,6 @@
elif o == "--fail-fast":
fail_fast = True
elif o == "--test":
- # print("asdfadf", o, a, a.split(","))
for _test in a.split(","):
if _test not in test_classes:
print("Invalid test name '{}'. Use option '--list' to show available tests".format(_test),
diff --git a/osm_nbi/tests/test_db_descriptors.py b/osm_nbi/tests/test_db_descriptors.py
index 1e6eff9..8b0b226 100644
--- a/osm_nbi/tests/test_db_descriptors.py
+++ b/osm_nbi/tests/test_db_descriptors.py
@@ -167,14 +167,12 @@
df:
- id: hackfest_default
- vnf-configuration-id: vnf-configuration-example
vdu-profile:
- id: mgmtVM
min-number-of-instances: 1
- id: dataVM
min-number-of-instances: 1
max-number-of-instances: 10
- vdu-configuration-id: vdu-configuration-example
instantiation-level:
- id: default
vdu-level:
@@ -210,32 +208,38 @@
vnf-config-primitive-name-ref: touch
- trigger: pre-scale-in
vnf-config-primitive-name-ref: touch
-
- vnf-configuration:
- - id: vnf-configuration-example
- initial-config-primitive:
- - seq: "1"
- name: config
- parameter:
- - name: ssh-hostname
- value: <rw_mgmt_ip>
- - name: ssh-username
- value: ubuntu
- - name: ssh-password
- value: osm4u
- - seq: "2"
- name: touch
- parameter:
- - name: filename
- value: <touch_filename>
- config-primitive:
- - name: touch
- parameter:
- - data-type: STRING
- default-value: <touch_filename2>
- name: filename
- juju:
- charm: simple
+ lcm-operations-configuration:
+ operate-vnf-op-config:
+ day1-2:
+ - id: hackfest3charmed-vnf
+ execution-environment-list:
+ - id: simple-ee
+ juju:
+ charm: simple
+ initial-config-primitive:
+ - seq: "1"
+ execution-environment-ref: simple-ee
+ name: config
+ parameter:
+ - name: ssh-hostname
+ value: <rw_mgmt_ip>
+ - name: ssh-username
+ value: ubuntu
+ - name: ssh-password
+ value: osm4u
+ - seq: "2"
+ execution-environment-ref: simple-ee
+ name: touch
+ parameter:
+ - name: filename
+ value: <touch_filename>
+ config-primitive:
+ - name: touch
+ execution-environment-ref: simple-ee
+ parameter:
+ - data-type: STRING
+ default-value: <touch_filename2>
+ name: filename
"""
db_nsds_text = """
diff --git a/osm_nbi/tests/test_descriptor_topics.py b/osm_nbi/tests/test_descriptor_topics.py
index 516c178..b52b6e0 100755
--- a/osm_nbi/tests/test_descriptor_topics.py
+++ b/osm_nbi/tests/test_descriptor_topics.py
@@ -112,11 +112,23 @@
self.assertEqual(db_args[1]["_admin"]["projects_read"], [test_pid], "Wrong read-only project list")
self.assertEqual(db_args[1]["_admin"]["projects_write"], [test_pid], "Wrong read-write project list")
tmp1 = test_vnfd["vdu"][0]["cloud-init-file"]
- tmp2 = test_vnfd["vnf-configuration"][0]["juju"]
+ tmp2 = test_vnfd["df"][
+ 0
+ ]["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"][
+ 0
+ ]["execution-environment-list"][
+ 0
+ ]["juju"]
del test_vnfd["vdu"][0]["cloud-init-file"]
- del test_vnfd["vnf-configuration"][0]["juju"]
+ del test_vnfd["df"][
+ 0
+ ]["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"][
+ 0
+ ]["execution-environment-list"][
+ 0
+ ]["juju"]
try:
- self.db.get_one.side_effect = [{"_id": did, "_admin": db_vnfd_content["_admin"]}, None]
+ self.db.get_one.side_effect = [{"_id": did, "_admin": deepcopy(db_vnfd_content["_admin"])}, None]
self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []})
msg_args = self.msg.write.call_args[0]
test_vnfd["_id"] = did
@@ -130,7 +142,7 @@
self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic")
self.assertEqual(db_args[1], did, "Wrong DB VNFD id")
admin = db_args[2]["_admin"]
- db_admin = db_vnfd_content["_admin"]
+ db_admin = deepcopy(db_vnfd_content["_admin"])
self.assertEqual(admin["type"], "vnfd", "Wrong descriptor type")
self.assertEqual(admin["created"], db_admin["created"], "Wrong creation time")
self.assertGreater(admin["modified"], db_admin["created"], "Wrong modification time")
@@ -145,9 +157,15 @@
compare_desc(self, test_vnfd, db_args[2], "VNFD")
finally:
test_vnfd["vdu"][0]["cloud-init-file"] = tmp1
- test_vnfd["vnf-configuration"][0]["juju"] = tmp2
+ test_vnfd["df"][
+ 0
+ ]["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"][
+ 0
+ ]["execution-environment-list"][
+ 0
+ ]["juju"] = tmp2
self.db.get_one.side_effect = lambda table, filter, fail_on_empty=None, fail_on_more=None: \
- {"_id": did, "_admin": db_vnfd_content["_admin"]}
+ {"_id": did, "_admin": deepcopy(db_vnfd_content["_admin"])}
with self.subTest(i=2, t='Check Pyangbind Validation: additional properties'):
test_vnfd["extra-property"] = 0
try:
@@ -178,14 +196,21 @@
self.assertIn(norm("{} defined in vnf[id={}]:vdu[id={}] but not present in package"
.format("cloud-init", test_vnfd["id"], test_vnfd["vdu"][0]["id"])),
norm(str(e.exception)), "Wrong exception text")
- with self.subTest(i=5, t='Check Input Validation: vnf-configuration[juju]'):
+ with self.subTest(i=5, t='Check Input Validation: day1-2 configuration[juju]'):
del test_vnfd["vdu"][0]["cloud-init-file"]
with self.assertRaises(EngineException, msg="Accepted non-existent charm in VNF configuration") as e:
self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []})
+ print(str(e.exception))
self.assertEqual(e.exception.http_code, HTTPStatus.BAD_REQUEST, "Wrong HTTP status code")
self.assertIn(norm("{} defined in vnf[id={}] but not present in package".format("charm", test_vnfd["id"])),
norm(str(e.exception)), "Wrong exception text")
- del test_vnfd["vnf-configuration"][0]["juju"]
+ del test_vnfd["df"][
+ 0
+ ]["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"][
+ 0
+ ]["execution-environment-list"][
+ 0
+ ]["juju"]
with self.subTest(i=6, t='Check Input Validation: mgmt-cp'):
tmp = test_vnfd["mgmt-cp"]
del test_vnfd["mgmt-cp"]
@@ -280,22 +305,30 @@
vdu['monitoring-parameter'] = tmp
with self.subTest(i=13, t='Check Input Validation: scaling-aspect vnf-configuration'):
df = test_vnfd['df'][0]
- tmp = test_vnfd.pop('vnf-configuration')
+ tmp = test_vnfd["df"][0]["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"].pop()
try:
with self.assertRaises(EngineException, msg="Accepted non-existent Scaling Group VDU ID Reference") \
as e:
self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []})
self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code")
- self.assertIn(norm("'vnf-configuration' not defined in the descriptor but it is referenced "
+ self.assertIn(norm("'day1-2 configuration' not defined in the descriptor but it is referenced "
"by df[id='{}']:scaling-aspect[id='{}']:scaling-config-action"
.format(df["id"], df['scaling-aspect'][0]["id"])),
norm(str(e.exception)), "Wrong exception text")
finally:
- test_vnfd["vnf-configuration"] = tmp
+ test_vnfd["df"][0]["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"].append(tmp)
with self.subTest(i=14, t='Check Input Validation: scaling-config-action'):
df = test_vnfd['df'][0]
- tmp = test_vnfd['vnf-configuration'][0]['config-primitive']
- test_vnfd['vnf-configuration'][0]['config-primitive'] = [{'name': 'wrong-primitive'}]
+ tmp = test_vnfd["df"][0].get(
+ "lcm-operations-configuration"
+ ).get(
+ "operate-vnf-op-config"
+ )["day1-2"][0]['config-primitive']
+ test_vnfd["df"][0].get(
+ "lcm-operations-configuration"
+ ).get(
+ "operate-vnf-op-config"
+ )["day1-2"][0]['config-primitive'] = [{'name': 'wrong-primitive'}]
try:
with self.assertRaises(EngineException,
msg="Accepted non-existent Scaling Group VDU ID Reference") as e:
@@ -303,15 +336,24 @@
self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code")
self.assertIn(norm("df[id='{}']:scaling-aspect[id='{}']:scaling-config-action:vnf-"
"config-primitive-name-ref='{}' does not match any "
- "vnf-configuration:config-primitive:name"
+ "day1-2 configuration:config-primitive:name"
.format(df["id"], df['scaling-aspect'][0]["id"],
sa['scaling-config-action'][0]['vnf-config-primitive-name-ref'])),
norm(str(e.exception)), "Wrong exception text")
finally:
- test_vnfd['vnf-configuration'][0]['config-primitive'] = tmp
+ test_vnfd["df"][0].get(
+ "lcm-operations-configuration"
+ ).get(
+ "operate-vnf-op-config"
+ )["day1-2"][0]['config-primitive'] = tmp
with self.subTest(i=15, t='Check Input Validation: everything right'):
test_vnfd["id"] = "fake-vnfd-id"
- self.db.get_one.side_effect = [{"_id": did, "_admin": db_vnfd_content["_admin"]}, None]
+ test_vnfd["df"][0].get(
+ "lcm-operations-configuration"
+ ).get(
+ "operate-vnf-op-config"
+ )["day1-2"][0]["id"] = "fake-vnfd-id"
+ self.db.get_one.side_effect = [{"_id": did, "_admin": deepcopy(db_vnfd_content["_admin"])}, None]
rc = self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []})
self.assertTrue(rc, "Input Validation: Unexpected failure")
return
@@ -323,8 +365,10 @@
self.fs.dir_ls.return_value = True
with self.subTest(i=1, t='Normal Edition'):
now = time()
- self.db.get_one.side_effect = [vnfd_content, None]
- data = {"id": "new-vnfd-id", "product-name": "new-vnfd-name"}
+ self.db.get_one.side_effect = [deepcopy(vnfd_content), None]
+ data = {
+ "product-name": "new-vnfd-name"
+ }
self.topic.edit(fake_session, did, data)
db_args = self.db.replace.call_args[0]
msg_args = self.msg.write.call_args[0]
@@ -342,11 +386,10 @@
"Wrong read-only project list")
self.assertEqual(db_args[2]["_admin"]["projects_write"], vnfd_content["_admin"]["projects_write"],
"Wrong read-write project list")
- self.assertEqual(db_args[2]["id"], data["id"], "Wrong VNFD ID")
self.assertEqual(db_args[2]["product-name"], data["product-name"], "Wrong VNFD Name")
with self.subTest(i=2, t='Conflict on Edit'):
- data = {"id": "fake-vnfd-id", "product-name": "new-vnfd-name"}
- self.db.get_one.side_effect = [vnfd_content, {"_id": str(uuid4()), "id": data["id"]}]
+ data = {"id": "hackfest3charmed-vnf", "product-name": "new-vnfd-name"}
+ self.db.get_one.side_effect = [deepcopy(vnfd_content), {"_id": str(uuid4()), "id": data["id"]}]
with self.assertRaises(EngineException, msg="Accepted existing VNFD ID") as e:
self.topic.edit(fake_session, did, data)
self.assertEqual(e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code")
@@ -613,11 +656,11 @@
indata = deepcopy(db_vnfd_content)
df = indata['df'][0]
affected_sa = df['scaling-aspect'][0]
- indata.pop('vnf-configuration')
+ indata["df"][0]["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"].pop()
with self.assertRaises(EngineException) as e:
self.topic.validate_scaling_group_descriptor(indata)
self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code")
- self.assertIn(norm("'vnf-configuration' not defined in the descriptor but it is referenced "
+ self.assertIn(norm("'day1-2 configuration' not defined in the descriptor but it is referenced "
"by df[id='{}']:scaling-aspect[id='{}']:scaling-config-action"
.format(df["id"], affected_sa["id"])),
norm(str(e.exception)), "Wrong exception text")
@@ -627,13 +670,13 @@
df = indata['df'][0]
affected_sa = df['scaling-aspect'][0]
affected_sca_primitive = affected_sa['scaling-config-action'][0]['vnf-config-primitive-name-ref']
- indata['vnf-configuration'][0]['config-primitive'] = []
+ df["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"][0]['config-primitive'] = []
with self.assertRaises(EngineException) as e:
self.topic.validate_scaling_group_descriptor(indata)
self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code")
self.assertIn(norm("df[id='{}']:scaling-aspect[id='{}']:scaling-config-action:vnf-"
"config-primitive-name-ref='{}' does not match any "
- "vnf-configuration:config-primitive:name"
+ "day1-2 configuration:config-primitive:name"
.format(df["id"], affected_sa["id"], affected_sca_primitive)),
norm(str(e.exception)), "Wrong exception text")
@@ -804,7 +847,7 @@
self.fs.dir_ls.return_value = True
with self.subTest(i=1, t='Normal Edition'):
now = time()
- self.db.get_one.side_effect = [nsd_content, None]
+ self.db.get_one.side_effect = [deepcopy(nsd_content), None]
self.db.get_list.return_value = [db_vnfd_content]
data = {"id": "new-nsd-id", "name": "new-nsd-name"}
self.topic.edit(fake_session, did, data)
@@ -989,7 +1032,7 @@
nsd_descriptor['df'][0]['vnf-profile'][1]['vnfd-id'] = invalid_vnfd_id
with self.assertRaises(EngineException) as e:
self.db.get_list.return_value = []
- self.topic.check_conflict_on_edit(fake_session, nsd_descriptor, [], 'id')
+ nsd_descriptor = self.topic.check_conflict_on_edit(fake_session, nsd_descriptor, [], 'id')
self.assertEqual(e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code")
self.assertIn(norm("Descriptor error at 'vnfd-id'='{}' references a non "
"existing vnfd".format(invalid_vnfd_id)),
diff --git a/osm_nbi/tests/test_pkg_descriptors.py b/osm_nbi/tests/test_pkg_descriptors.py
index 4552e24..91e5641 100644
--- a/osm_nbi/tests/test_pkg_descriptors.py
+++ b/osm_nbi/tests/test_pkg_descriptors.py
@@ -134,14 +134,12 @@
df:
- id: hackfest_default
- vnf-configuration-id: vnf-configuration-example
vdu-profile:
- id: mgmtVM
min-number-of-instances: 1
- id: dataVM
min-number-of-instances: 1
max-number-of-instances: 10
- vdu-configuration-id: vdu-configuration-example
instantiation-level:
- id: default
vdu-level:
@@ -177,32 +175,38 @@
vnf-config-primitive-name-ref: touch
- trigger: pre-scale-in
vnf-config-primitive-name-ref: touch
-
- vnf-configuration:
- - id: vnf-configuration-example
- initial-config-primitive:
- - seq: "1"
- name: config
- parameter:
- - name: ssh-hostname
- value: <rw_mgmt_ip>
- - name: ssh-username
- value: ubuntu
- - name: ssh-password
- value: osm4u
- - seq: "2"
- name: touch
- parameter:
- - name: filename
- value: <touch_filename>
- config-primitive:
- - name: touch
- parameter:
- - data-type: STRING
- default-value: <touch_filename2>
- name: filename
- juju:
- charm: simple
+ lcm-operations-configuration:
+ operate-vnf-op-config:
+ day1-2:
+ - id: hackfest3charmed-vnf
+ execution-environment-list:
+ - id: simple-ee
+ juju:
+ charm: simple
+ initial-config-primitive:
+ - seq: "1"
+ execution-environment-ref: simple-ee
+ name: config
+ parameter:
+ - name: ssh-hostname
+ value: <rw_mgmt_ip>
+ - name: ssh-username
+ value: ubuntu
+ - name: ssh-password
+ value: osm4u
+ - seq: "2"
+ execution-environment-ref: simple-ee
+ name: touch
+ parameter:
+ - name: filename
+ value: <touch_filename>
+ config-primitive:
+ - name: touch
+ execution-environment-ref: simple-ee
+ parameter:
+ - data-type: STRING
+ default-value: <touch_filename2>
+ name: filename
"""
db_nsds_text = """
diff --git a/osm_nbi/utils.py b/osm_nbi/utils.py
index bb9e33e..73fc40f 100644
--- a/osm_nbi/utils.py
+++ b/osm_nbi/utils.py
@@ -31,9 +31,36 @@
return None
+def filter_in_list(the_list, condition_lambda):
+ ret = []
+ for item in the_list:
+ if condition_lambda(item):
+ ret.append(item)
+ return ret
+
+
def find_index_in_list(the_list, condition_lambda):
for index, item in enumerate(the_list):
if condition_lambda(item):
return index
else:
return -1
+
+
+def deep_update_dict(data, updated_data):
+ if isinstance(data, list):
+ processed_items_data = []
+ for index, item in enumerate(data):
+ processed_items_data.append(deep_update_dict(item, updated_data[index]))
+ return processed_items_data
+
+ if isinstance(data, dict):
+ for key in data.keys():
+ if key in updated_data:
+ if not isinstance(data[key], dict) and not isinstance(data[key], list):
+ data[key] = updated_data[key]
+ else:
+ data[key] = deep_update_dict(data[key], updated_data[key])
+ return data
+
+ return data