# limitations under the License.
# import logging
+import json
from uuid import uuid4
from http import HTTPStatus
from time import time
ns_terminate,
ns_action,
ns_scale,
+ ns_update,
+ ns_heal,
nsi_instantiate,
+ ns_migrate,
+ nslcmop_cancel,
)
from osm_nbi.base_topic import (
BaseTopic,
get_iterable,
deep_get,
increment_ip_mac,
+ update_descriptor_usage_state,
)
from yaml import safe_dump
from osm_common.dbbase import DbException
def __init__(self, db, fs, msg, auth):
BaseTopic.__init__(self, db, fs, msg, auth)
- def _check_descriptor_dependencies(self, session, descriptor):
- """
- Check that the dependent descriptors exist on a new descriptor or edition
- :param session: client session information
- :param descriptor: descriptor to be inserted or edit
- :return: None or raises exception
- """
- if not descriptor.get("nsdId"):
- return
- nsd_id = descriptor["nsdId"]
- if not self.get_item_list(session, "nsds", {"id": nsd_id}):
- raise EngineException(
- "Descriptor error at nsdId='{}' references a non exist nsd".format(
- nsd_id
- ),
- http_code=HTTPStatus.CONFLICT,
- )
-
@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)
"nsds", {"_id": used_nsd_id}, {"_admin.usageState": "NOT_IN_USE"}
)
+ # Set NS CONFIG TEMPLATE usageState
+ if nsr.get("instantiate_params", {}).get("nsConfigTemplateId"):
+ nsconfigtemplate_id = nsr.get("instantiate_params", {}).get(
+ "nsConfigTemplateId"
+ )
+ nsconfigtemplate_list = self.db.get_one(
+ "nsrs",
+ {"instantiate_params.nsConfigTemplateId": nsconfigtemplate_id},
+ fail_on_empty=False,
+ fail_on_more=False,
+ )
+ if not nsconfigtemplate_list:
+ self.db.set_one(
+ "ns_config_template",
+ {"_id": nsconfigtemplate_id},
+ {"_admin.usageState": "NOT_IN_USE"},
+ )
+
# Set VNFD usageState
used_vnfd_id_list = nsr.get("vnfd-id")
if used_vnfd_id_list:
ns_request, member_vnf_index=None, vdu_id=None, kdu_name=None, descriptor=None
):
"""
- Get and format user additional params for NS or VNF
+ Get and format user additional params for NS or VNF.
+ The vdu_id and kdu_name params are mutually exclusive! If none of them are given, then the method will
+ exclusively search for the VNF/NS LCM additional params.
+
:param ns_request: User instantiation additional parameters
:param member_vnf_index: None for extract NS params, or member_vnf_index to extract VNF params
+ :vdu_id: VDU's ID against which we want to format the additional params
+ :kdu_name: KDU's name against which we want to format the additional params
:param descriptor: If not None it check that needed parameters of descriptor are supplied
:return: tuple with a formatted copy of additional params or None if not supplied, plus other parameters
"""
where_, k
)
)
- if "." in k or "$" in k:
+ if "$" in k:
raise EngineException(
- "Invalid param at {}:{}. Keys must not contain dots or $".format(
+ "Invalid param at {}:{}. Keys must not contain $ symbol".format(
where_, k
)
)
if isinstance(v, (dict, tuple, list)):
additional_params[k] = "!!yaml " + safe_dump(v)
+ if kdu_name:
+ additional_params = json.dumps(additional_params)
+
+ # Select the VDU ID, KDU name or NS/VNF ID, depending on the method's call intent
+ selector = vdu_id if vdu_id else kdu_name if kdu_name else descriptor.get("id")
if descriptor:
for df in descriptor.get("df", []):
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)
+ # Verify the target object (VNF|NS|VDU|KDU) where we need to populate
+ # the params with the additional ones given by the user
+ if config.get("id") == selector:
+ 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")
"<rw_mgmt_ip>",
"<VDU_SCALE_INFO>",
"<ns_config_info>",
+ "<OSM>",
):
continue
if (
EngineException, ValidationError, DbException, FsException, MsgException.
Note: Exceptions are not captured on purpose. They should be captured at called
"""
+ step = "checking quotas" # first step must be defined outside try
try:
- step = "checking quotas"
self.check_quota(session)
step = "validating input parameters"
nsd = self._get_nsd_from_db(ns_request["nsdId"], session)
ns_k8s_namespace = self._get_ns_k8s_namespace(nsd, ns_request, session)
+ # Uploading the instantiation parameters to ns_request from ns config template
+ if ns_request.get("nsConfigTemplateId"):
+ step = "getting ns_config_template is='{}' from database".format(
+ ns_request.get("nsConfigTemplateId")
+ )
+ ns_config_template_db = self._get_nsConfigTemplate_from_db(
+ ns_request.get("nsConfigTemplateId"), session
+ )
+ ns_config_params = ns_config_template_db.get("config")
+ for key, value in ns_config_params.items():
+ if key == "vnf":
+ ns_request["vnf"] = ns_config_params.get("vnf")
+ elif key == "additionalParamsForVnf":
+ ns_request["additionalParamsForVnf"] = ns_config_params.get(
+ "additionalParamsForVnf"
+ )
+ elif key == "additionalParamsForNs":
+ ns_request["additionalParamsForNs"] = ns_config_params.get(
+ "additionalParamsForNs"
+ )
+ elif key == "vld":
+ ns_request["vld"] = ns_config_params.get("vld")
+ step = "checking ns_config_templateOperationalState"
+ self._check_ns_config_template_operational_state(
+ ns_config_template_db, ns_request
+ )
+
+ step = "Updating NSCONFIG TEMPLATE usageState"
+ update_descriptor_usage_state(
+ ns_config_template_db, "ns_config_template", self.db
+ )
+
step = "checking nsdOperationalState"
self._check_nsd_operational_state(nsd, ns_request)
)
if vnfd_id not in needed_vnfds:
vnfd = self._get_vnfd_from_db(vnfd_id, session)
+ if "revision" in vnfd["_admin"]:
+ vnfd["revision"] = vnfd["_admin"]["revision"]
+ vnfd.pop("_admin")
needed_vnfds[vnfd_id] = vnfd
nsr_descriptor["vnfd-id"].append(vnfd["_id"])
else:
)
self._add_vnfr_to_db(vnfr_descriptor, rollback, session)
nsr_descriptor["constituent-vnfr-ref"].append(vnfr_descriptor["id"])
+ step = "Updating VNFD usageState"
+ update_descriptor_usage_state(vnfd, "vnfds", self.db)
step = "creating nsr at database"
self._add_nsr_to_db(nsr_descriptor, rollback, session)
+ step = "Updating NSD usageState"
+ update_descriptor_usage_state(nsd, "nsds", self.db)
step = "creating nsr temporal folder"
self.fs.mkdir(nsr_id)
_filter["_id"] = nsd_id
return self.db.get_one("nsds", _filter)
+ def _get_nsConfigTemplate_from_db(self, nsConfigTemplate_id, session):
+ _filter = self._get_project_filter(session)
+ _filter["_id"] = nsConfigTemplate_id
+ ns_config_template_db = self.db.get_one(
+ "ns_config_template", _filter, fail_on_empty=False
+ )
+ return ns_config_template_db
+
def _get_vnfd_from_db(self, vnfd_id, session):
_filter = self._get_project_filter(session)
_filter["id"] = vnfd_id
vnfd = self.db.get_one("vnfds", _filter, fail_on_empty=True, fail_on_more=True)
- vnfd.pop("_admin")
return vnfd
def _add_nsr_to_db(self, nsr_descriptor, rollback, session):
http_code=HTTPStatus.CONFLICT,
)
+ def _check_ns_config_template_operational_state(
+ self, ns_config_template_db, ns_request
+ ):
+ if ns_config_template_db["_admin"]["operationalState"] == "DISABLED":
+ raise EngineException(
+ "ns_config_template with id '{}' is DISABLED, and thus cannot be used to create "
+ "a network service".format(ns_request["nsConfigTemplateId"]),
+ http_code=HTTPStatus.CONFLICT,
+ )
+
def _get_ns_k8s_namespace(self, nsd, ns_request, session):
additional_params, _ = self._format_additional_params(
ns_request, descriptor=nsd
return ns_k8s_namespace
+ def _add_shared_volumes_to_nsr(
+ self, vdu, vnfd, nsr_descriptor, member_vnf_index, revision=None
+ ):
+ svsd = []
+ for vsd in vnfd.get("virtual-storage-desc", ()):
+ if vsd.get("vdu-storage-requirements"):
+ if (
+ vsd.get("vdu-storage-requirements")[0].get("key") == "multiattach"
+ and vsd.get("vdu-storage-requirements")[0].get("value") == "True"
+ ):
+ # Avoid setting the volume name multiple times
+ if not match(f"shared-.*-{vnfd['id']}", vsd["id"]):
+ vsd["id"] = f"shared-{vsd['id']}-{vnfd['id']}"
+ svsd.append(vsd)
+ if svsd:
+ nsr_descriptor["shared-volumes"] = svsd
+
+ def _add_flavor_to_nsr(
+ self, vdu, vnfd, nsr_descriptor, member_vnf_index, revision=None
+ ):
+ flavor_data = {}
+ guest_epa = {}
+ # Find this vdu compute and storage descriptors
+ vdu_virtual_compute = {}
+ vdu_virtual_storage = {}
+ for vcd in vnfd.get("virtual-compute-desc", ()):
+ if vcd.get("id") == vdu.get("virtual-compute-desc"):
+ vdu_virtual_compute = vcd
+ for vsd in vnfd.get("virtual-storage-desc", ()):
+ if vsd.get("id") == vdu.get("virtual-storage-desc", [[]])[0]:
+ vdu_virtual_storage = vsd
+ # Get this vdu vcpus, memory and storage info for flavor_data
+ if vdu_virtual_compute.get("virtual-cpu", {}).get("num-virtual-cpu"):
+ flavor_data["vcpu-count"] = vdu_virtual_compute["virtual-cpu"][
+ "num-virtual-cpu"
+ ]
+ if vdu_virtual_compute.get("virtual-memory", {}).get("size"):
+ flavor_data["memory-mb"] = (
+ float(vdu_virtual_compute["virtual-memory"]["size"]) * 1024.0
+ )
+ if vdu_virtual_storage.get("size-of-storage"):
+ flavor_data["storage-gb"] = vdu_virtual_storage["size-of-storage"]
+ # Get this vdu EPA info for guest_epa
+ if vdu_virtual_compute.get("virtual-cpu", {}).get("cpu-quota"):
+ guest_epa["cpu-quota"] = vdu_virtual_compute["virtual-cpu"]["cpu-quota"]
+ if vdu_virtual_compute.get("virtual-cpu", {}).get("pinning"):
+ vcpu_pinning = vdu_virtual_compute["virtual-cpu"]["pinning"]
+ if vcpu_pinning.get("thread-policy"):
+ guest_epa["cpu-thread-pinning-policy"] = vcpu_pinning["thread-policy"]
+ if vcpu_pinning.get("policy"):
+ cpu_policy = (
+ "SHARED" if vcpu_pinning["policy"] == "dynamic" else "DEDICATED"
+ )
+ guest_epa["cpu-pinning-policy"] = cpu_policy
+ if vdu_virtual_compute.get("virtual-memory", {}).get("mem-quota"):
+ guest_epa["mem-quota"] = vdu_virtual_compute["virtual-memory"]["mem-quota"]
+ if vdu_virtual_compute.get("virtual-memory", {}).get("mempage-size"):
+ guest_epa["mempage-size"] = vdu_virtual_compute["virtual-memory"][
+ "mempage-size"
+ ]
+ if vdu_virtual_compute.get("virtual-memory", {}).get("numa-node-policy"):
+ guest_epa["numa-node-policy"] = vdu_virtual_compute["virtual-memory"][
+ "numa-node-policy"
+ ]
+ if vdu_virtual_storage.get("disk-io-quota"):
+ guest_epa["disk-io-quota"] = vdu_virtual_storage["disk-io-quota"]
+
+ if guest_epa:
+ flavor_data["guest-epa"] = guest_epa
+
+ revision = revision if revision is not None else 1
+ flavor_data["name"] = (
+ vdu["id"][:56] + "-" + member_vnf_index + "-" + str(revision) + "-flv"
+ )
+ flavor_data["id"] = str(len(nsr_descriptor["flavor"]))
+ nsr_descriptor["flavor"].append(flavor_data)
+
def _create_nsr_descriptor_from_nsd(self, nsd, ns_request, nsr_id, session):
now = time()
additional_params, _ = self._format_additional_params(
"ssh-authorized-key": ns_request.get("ssh_keys"), # TODO remove
"flavor": [],
"image": [],
+ "affinity-or-anti-affinity-group": [],
+ "shared-volumes": [],
+ "vnffgd": [],
}
+ if "revision" in nsd["_admin"]:
+ nsr_descriptor["revision"] = nsd["_admin"]["revision"]
+
ns_request["nsr_id"] = nsr_id
if ns_request and ns_request.get("config-units"):
nsr_descriptor["config-units"] = ns_request["config-units"]
)
vnfd = self._get_vnfd_from_db(vnf_profile.get("vnfd-id"), session)
+ vnfd.pop("_admin")
for vdu in vnfd.get("vdu", ()):
- flavor_data = {}
- guest_epa = {}
- # Find this vdu compute and storage descriptors
- vdu_virtual_compute = {}
- vdu_virtual_storage = {}
- for vcd in vnfd.get("virtual-compute-desc", ()):
- if vcd.get("id") == vdu.get("virtual-compute-desc"):
- vdu_virtual_compute = vcd
- for vsd in vnfd.get("virtual-storage-desc", ()):
- if vsd.get("id") == vdu.get("virtual-storage-desc", [[]])[0]:
- vdu_virtual_storage = vsd
- # Get this vdu vcpus, memory and storage info for flavor_data
- if vdu_virtual_compute.get("virtual-cpu", {}).get(
- "num-virtual-cpu"
- ):
- flavor_data["vcpu-count"] = vdu_virtual_compute["virtual-cpu"][
- "num-virtual-cpu"
- ]
- if vdu_virtual_compute.get("virtual-memory", {}).get("size"):
- flavor_data["memory-mb"] = (
- float(vdu_virtual_compute["virtual-memory"]["size"])
- * 1024.0
- )
- if vdu_virtual_storage.get("size-of-storage"):
- flavor_data["storage-gb"] = vdu_virtual_storage[
- "size-of-storage"
- ]
- # Get this vdu EPA info for guest_epa
- if vdu_virtual_compute.get("virtual-cpu", {}).get("cpu-quota"):
- guest_epa["cpu-quota"] = vdu_virtual_compute["virtual-cpu"][
- "cpu-quota"
- ]
- if vdu_virtual_compute.get("virtual-cpu", {}).get("pinning"):
- vcpu_pinning = vdu_virtual_compute["virtual-cpu"]["pinning"]
- if vcpu_pinning.get("thread-policy"):
- guest_epa["cpu-thread-pinning-policy"] = vcpu_pinning[
- "thread-policy"
- ]
- if vcpu_pinning.get("policy"):
- cpu_policy = (
- "SHARED"
- if vcpu_pinning["policy"] == "dynamic"
- else "DEDICATED"
- )
- guest_epa["cpu-pinning-policy"] = cpu_policy
- if vdu_virtual_compute.get("virtual-memory", {}).get("mem-quota"):
- guest_epa["mem-quota"] = vdu_virtual_compute["virtual-memory"][
- "mem-quota"
- ]
- if vdu_virtual_compute.get("virtual-memory", {}).get(
- "mempage-size"
- ):
- guest_epa["mempage-size"] = vdu_virtual_compute[
- "virtual-memory"
- ]["mempage-size"]
- if vdu_virtual_compute.get("virtual-memory", {}).get(
- "numa-node-policy"
- ):
- guest_epa["numa-node-policy"] = vdu_virtual_compute[
- "virtual-memory"
- ]["numa-node-policy"]
- if vdu_virtual_storage.get("disk-io-quota"):
- guest_epa["disk-io-quota"] = vdu_virtual_storage[
- "disk-io-quota"
- ]
-
- if guest_epa:
- flavor_data["guest-epa"] = guest_epa
-
- flavor_data["name"] = vdu["id"][:56] + "-flv"
- flavor_data["id"] = str(len(nsr_descriptor["flavor"]))
- nsr_descriptor["flavor"].append(flavor_data)
-
+ member_vnf_index = vnf_profile.get("id")
+ self._add_flavor_to_nsr(vdu, vnfd, nsr_descriptor, member_vnf_index)
+ self._add_shared_volumes_to_nsr(
+ vdu, vnfd, nsr_descriptor, member_vnf_index
+ )
sw_image_id = vdu.get("sw-image-desc")
if sw_image_id:
image_data = self._get_image_data_from_vnfd(vnfd, sw_image_id)
image_data = self._get_image_data_from_vnfd(vnfd, alt_image)
self._add_image_to_nsr(nsr_descriptor, image_data)
+ # Add Affinity or Anti-affinity group information to NSR
+ vdu_profiles = vnfd.get("df", [[]])[0].get("vdu-profile", ())
+ affinity_group_prefix_name = "{}-{}".format(
+ nsr_descriptor["name"][:16], vnf_profile.get("id")[:16]
+ )
+
+ for vdu_profile in vdu_profiles:
+ affinity_group_data = {}
+ for affinity_group in vdu_profile.get(
+ "affinity-or-anti-affinity-group", ()
+ ):
+ affinity_group_data = (
+ self._get_affinity_or_anti_affinity_group_data_from_vnfd(
+ vnfd, affinity_group["id"]
+ )
+ )
+ affinity_group_data["member-vnf-index"] = vnf_profile.get("id")
+ self._add_affinity_or_anti_affinity_group_to_nsr(
+ nsr_descriptor,
+ affinity_group_data,
+ affinity_group_prefix_name,
+ )
+
for vld in nsr_vld:
vld["vnfd-connection-point-ref"] = all_vld_connection_point_data.get(
vld.get("id"), []
)
vld["name"] = vld["id"]
nsr_descriptor["vld"] = nsr_vld
+ if nsd.get("vnffgd"):
+ vnffgd = nsd.get("vnffgd")
+ for vnffg in vnffgd:
+ info = {}
+ for k, v in vnffg.items():
+ if k == "id":
+ info.update({k: v})
+ if k == "nfpd":
+ info.update({k: v})
+ nsr_descriptor["vnffgd"].append(info)
return nsr_descriptor
+ def _get_affinity_or_anti_affinity_group_data_from_vnfd(
+ self, vnfd, affinity_group_id
+ ):
+ """
+ Gets affinity-or-anti-affinity-group info from df and returns the desired affinity group
+ """
+ affinity_group = utils.find_in_list(
+ vnfd.get("df", [[]])[0].get("affinity-or-anti-affinity-group", ()),
+ lambda ag: ag["id"] == affinity_group_id,
+ )
+ affinity_group_data = {}
+ if affinity_group:
+ if affinity_group.get("id"):
+ affinity_group_data["ag-id"] = affinity_group["id"]
+ if affinity_group.get("type"):
+ affinity_group_data["type"] = affinity_group["type"]
+ if affinity_group.get("scope"):
+ affinity_group_data["scope"] = affinity_group["scope"]
+ return affinity_group_data
+
+ def _add_affinity_or_anti_affinity_group_to_nsr(
+ self, nsr_descriptor, affinity_group_data, affinity_group_prefix_name
+ ):
+ """
+ Adds affinity-or-anti-affinity-group to nsr checking first it is not already added
+ """
+ affinity_group = next(
+ (
+ f
+ for f in nsr_descriptor["affinity-or-anti-affinity-group"]
+ if all(f.get(k) == affinity_group_data[k] for k in affinity_group_data)
+ ),
+ None,
+ )
+ if not affinity_group:
+ affinity_group_data["id"] = str(
+ len(nsr_descriptor["affinity-or-anti-affinity-group"])
+ )
+ affinity_group_data["name"] = "{}-{}".format(
+ affinity_group_prefix_name, affinity_group_data["ag-id"][:32]
+ )
+ nsr_descriptor["affinity-or-anti-affinity-group"].append(
+ affinity_group_data
+ )
+
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
nsr_descriptor,
ns_request,
ns_k8s_namespace,
+ revision=None,
):
vnfr_id = str(uuid4())
nsr_id = nsr_descriptor["id"]
"connection-point": [],
"ip-address": None, # mgmt-interface filled by LCM
}
+
+ # Revision backwards compatility. Only specify the revision in the record if
+ # the original VNFD has a revision.
+ if "revision" in vnfd:
+ vnfr_descriptor["revision"] = vnfd["revision"]
+
vnf_k8s_namespace = ns_k8s_namespace
if vnf_params:
if vnf_params.get("k8s-namespace"):
additional_params, vdu_params = self._format_additional_params(
ns_request, vnf_index, vdu_id=vdu["id"], descriptor=vnfd
)
+
+ try:
+ vdu_virtual_storage_descriptors = utils.filter_in_list(
+ vnfd.get("virtual-storage-desc", []),
+ lambda stg_desc: stg_desc["id"] in vdu["virtual-storage-desc"],
+ )
+ except Exception:
+ vdu_virtual_storage_descriptors = []
vdur = {
"vdu-id-ref": vdu["id"],
# TODO "name": "" Name of the VDU in the VIM
"interfaces": [],
"additionalParams": additional_params,
"vdu-name": vdu["name"],
+ "virtual-storages": vdu_virtual_storage_descriptors,
}
if vdu_params and vdu_params.get("config-units"):
vdur["config-units"] = vdu_params["config-units"]
vdur["internal-connection-point"].append(vdu_icp)
for iface in icp.get("virtual-network-interface-requirement", ()):
- iface_fields = ("name", "mac-address")
+ # Name, mac-address and interface position is taken from VNFD
+ # and included into VNFR. By this way RO can process this information
+ # while creating the VDU.
+ iface_fields = ("name", "mac-address", "position", "ip-address")
vdu_iface = {
x: iface[x] for x in iface_fields if iface.get(x) is not None
}
if (
cpd.get("constituent-cpd-id")
== iface_ext_cp
- ):
+ ) and vnf_profile.get("id") == vnf_index:
vdu_iface["ns-vld-id"] = vlc.get(
"virtual-link-profile-id"
)
alt_image_ids.append(nsr_sw_image_data["id"])
vdur["alt-image-ids"] = alt_image_ids
- flavor_data_name = vdu["id"][:56] + "-flv"
+ revision = revision if revision is not None else 1
+ flavor_data_name = (
+ vdu["id"][:56] + "-" + vnf_index + "-" + str(revision) + "-flv"
+ )
nsr_flavor_desc = utils.find_in_list(
nsr_descriptor["flavor"],
lambda flavor: flavor["name"] == flavor_data_name,
if nsr_flavor_desc:
vdur["ns-flavor-id"] = nsr_flavor_desc["id"]
+ # Adding Shared Volume information to vdur
+ if vdur.get("virtual-storages"):
+ nsr_sv = []
+ for vsd in vdur["virtual-storages"]:
+ if vsd.get("vdu-storage-requirements"):
+ if (
+ vsd["vdu-storage-requirements"][0].get("key")
+ == "multiattach"
+ and vsd["vdu-storage-requirements"][0].get("value")
+ == "True"
+ ):
+ nsr_sv.append(vsd["id"])
+ if nsr_sv:
+ vdur["shared-volumes-id"] = nsr_sv
+
+ # Adding Affinity groups information to vdur
+ try:
+ vdu_profile_affinity_group = utils.find_in_list(
+ vnfd.get("df")[0]["vdu-profile"],
+ lambda a_vdu: a_vdu["id"] == vdu["id"],
+ )
+ except Exception:
+ vdu_profile_affinity_group = None
+
+ if vdu_profile_affinity_group:
+ affinity_group_ids = []
+ for affinity_group in vdu_profile_affinity_group.get(
+ "affinity-or-anti-affinity-group", ()
+ ):
+ vdu_affinity_group = utils.find_in_list(
+ vdu_profile_affinity_group.get(
+ "affinity-or-anti-affinity-group", ()
+ ),
+ lambda ag_fp: ag_fp["id"] == affinity_group["id"],
+ )
+ nsr_affinity_group = utils.find_in_list(
+ nsr_descriptor["affinity-or-anti-affinity-group"],
+ lambda nsr_ag: (
+ nsr_ag.get("ag-id") == vdu_affinity_group.get("id")
+ and nsr_ag.get("member-vnf-index")
+ == vnfr_descriptor.get("member-vnf-index-ref")
+ ),
+ )
+ # Update Affinity Group VIM name if VDU instantiation parameter is present
+ if vnf_params and vnf_params.get("affinity-or-anti-affinity-group"):
+ vnf_params_affinity_group = utils.find_in_list(
+ vnf_params["affinity-or-anti-affinity-group"],
+ lambda vnfp_ag: (
+ vnfp_ag.get("id") == vdu_affinity_group.get("id")
+ ),
+ )
+ if vnf_params_affinity_group.get("vim-affinity-group-id"):
+ nsr_affinity_group[
+ "vim-affinity-group-id"
+ ] = vnf_params_affinity_group["vim-affinity-group-id"]
+ affinity_group_ids.append(nsr_affinity_group["id"])
+ vdur["affinity-or-anti-affinity-group-id"] = affinity_group_ids
+
if vdu_instantiation_level:
count = vdu_instantiation_level.get("number-of-instances")
else:
vdur["id"] = vdur["_id"]
vdur["count-index"] = index
vnfr_descriptor["vdur"].append(vdur)
-
return vnfr_descriptor
def vca_status_refresh(self, session, ns_instance_content, filter_q):
:param filter_q: dict: query parameter containing vcaStatus-refresh as true or false
:return: None
"""
- time_now, time_delta = time(), time() - ns_instance_content["_admin"]["modified"]
- force_refresh = isinstance(filter_q, dict) and filter_q.get('vcaStatusRefresh') == 'true'
+ time_now, time_delta = (
+ time(),
+ time() - ns_instance_content["_admin"]["modified"],
+ )
+ force_refresh = (
+ isinstance(filter_q, dict) and filter_q.get("vcaStatusRefresh") == "true"
+ )
threshold_reached = time_delta > 120
if force_refresh or threshold_reached:
operation, _id = "vca_status_refresh", ns_instance_content["_id"]
ns_instance_content["_admin"]["modified"] = time_now
self.db.set_one(self.topic, {"_id": _id}, ns_instance_content)
nslcmop_desc = NsLcmOpTopic._create_nslcmop(_id, operation, None)
- self.format_on_new(nslcmop_desc, session["project_id"], make_public=session["public"])
+ self.format_on_new(
+ nslcmop_desc, session["project_id"], make_public=session["public"]
+ )
nslcmop_desc["_admin"].pop("nsState")
self.msg.write("ns", operation, nslcmop_desc)
return
operation_schema = { # mapping between operation and jsonschema to validate
"instantiate": ns_instantiate,
"action": ns_action,
+ "update": ns_update,
"scale": ns_scale,
+ "heal": ns_heal,
"terminate": ns_terminate,
+ "migrate": ns_migrate,
+ "cancel": nslcmop_cancel,
}
def __init__(self, db, fs, msg, auth):
BaseTopic.__init__(self, db, fs, msg, auth)
+ self.nsrtopic = NsrTopic(db, fs, msg, auth)
def _check_ns_operation(self, session, nsr, operation, indata):
"""
Check that user has enter right parameters for the operation
:param session: contains "username", "admin", "force", "public", "project_id", "set_project"
- :param operation: it can be: instantiate, terminate, action, TODO: update, heal
+ :param operation: it can be: instantiate, terminate, action, update, heal
:param indata: descriptor with the parameters of the operation
:return: None
"""
self._check_action_ns_operation(indata, nsr)
elif operation == "scale":
self._check_scale_ns_operation(indata, nsr)
+ elif operation == "update":
+ self._check_update_ns_operation(indata, nsr)
+ elif operation == "heal":
+ self._check_heal_ns_operation(indata, nsr)
elif operation == "instantiate":
self._check_instantiate_ns_operation(indata, nsr, session)
)
)
+ def _check_update_ns_operation(self, indata, nsr) -> None:
+ """Validates the ns-update request according to updateType
+
+ If updateType is CHANGE_VNFPKG:
+ - it checks the vnfInstanceId, whether it's available under ns instance
+ - it checks the vnfdId whether it matches with the vnfd-id in the vnf-record of specified VNF.
+ Otherwise exception will be raised.
+ If updateType is REMOVE_VNF:
+ - it checks if the vnfInstanceId is available in the ns instance
+ - Otherwise exception will be raised.
+ If updateType is OPERATE_VNF
+ - it checks if the vdu-id is persent in the descriptor or not
+ - it checks if the changeStateTo is either start, stop or rebuild
+ If updateType is VERTICAL_SCALE
+ - it checks if the vdu-id is persent in the descriptor or not
+
+ Args:
+ indata: includes updateType such as CHANGE_VNFPKG,
+ nsr: network service record
+
+ Raises:
+ EngineException:
+ a meaningful error if given update parameters are not proper such as
+ "Error in validating ns-update request: <ID> does not match
+ with the vnfd-id of vnfinstance
+ http_code=HTTPStatus.UNPROCESSABLE_ENTITY"
+
+ """
+ try:
+ if indata["updateType"] == "CHANGE_VNFPKG":
+ # vnfInstanceId, nsInstanceId, vnfdId are mandatory
+ vnf_instance_id = indata["changeVnfPackageData"]["vnfInstanceId"]
+ ns_instance_id = indata["nsInstanceId"]
+ vnfd_id_2update = indata["changeVnfPackageData"]["vnfdId"]
+
+ if vnf_instance_id not in nsr["constituent-vnfr-ref"]:
+ raise EngineException(
+ f"Error in validating ns-update request: vnf {vnf_instance_id} does not "
+ f"belong to NS {ns_instance_id}",
+ http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+ )
+
+ # Getting vnfrs through the ns_instance_id
+ vnfrs = self.db.get_list("vnfrs", {"nsr-id-ref": ns_instance_id})
+ constituent_vnfd_id = next(
+ (
+ vnfr["vnfd-id"]
+ for vnfr in vnfrs
+ if vnfr["id"] == vnf_instance_id
+ ),
+ None,
+ )
+
+ # Check the given vnfd-id belongs to given vnf instance
+ if constituent_vnfd_id and (vnfd_id_2update != constituent_vnfd_id):
+ raise EngineException(
+ f"Error in validating ns-update request: vnfd-id {vnfd_id_2update} does not "
+ f"match with the vnfd-id: {constituent_vnfd_id} of VNF instance: {vnf_instance_id}",
+ http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+ )
+
+ # Validating the ns update timeout
+ if (
+ indata.get("timeout_ns_update")
+ and indata["timeout_ns_update"] < 300
+ ):
+ raise EngineException(
+ "Error in validating ns-update request: {} second is not enough "
+ "to upgrade the VNF instance: {}".format(
+ indata["timeout_ns_update"], vnf_instance_id
+ ),
+ http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+ )
+ elif indata["updateType"] == "REMOVE_VNF":
+ vnf_instance_id = indata["removeVnfInstanceId"]
+ ns_instance_id = indata["nsInstanceId"]
+ if vnf_instance_id not in nsr["constituent-vnfr-ref"]:
+ raise EngineException(
+ "Invalid VNF Instance Id. '{}' is not "
+ "present in the NS '{}'".format(vnf_instance_id, ns_instance_id)
+ )
+ elif indata["updateType"] == "OPERATE_VNF":
+ if indata.get("operateVnfData"):
+ if indata["operateVnfData"]["changeStateTo"] not in (
+ "start",
+ "stop",
+ "rebuild",
+ ):
+ raise EngineException(
+ f"The operate type should be either start, stop or rebuild not {indata['operateVnfData']['changeStateTo']}",
+ http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+ )
+ if indata["operateVnfData"].get("additionalParam"):
+ vdu_id = indata["operateVnfData"]["additionalParam"]["vdu_id"]
+ vnfinstance_id = indata["operateVnfData"]["vnfInstanceId"]
+ vnf = self.db.get_one("vnfrs", {"_id": vnfinstance_id})
+ vnfd_member_vnf_index = vnf.get("member-vnf-index-ref")
+ vnfd = self._get_vnfd_from_vnf_member_index(
+ vnfd_member_vnf_index, nsr["_id"]
+ )
+ self._check_valid_vdu(vnfd, vdu_id)
+ elif indata["updateType"] == "VERTICAL_SCALE":
+ if indata.get("verticalScaleVnf"):
+ vdu_id = indata["verticalScaleVnf"]["vduId"]
+ vnfinstance_id = indata["verticalScaleVnf"]["vnfInstanceId"]
+ vnf = self.db.get_one("vnfrs", {"_id": vnfinstance_id})
+ vnfd_member_vnf_index = vnf.get("member-vnf-index-ref")
+ vnfd = self._get_vnfd_from_vnf_member_index(
+ vnfd_member_vnf_index, nsr["_id"]
+ )
+ self._check_valid_vdu(vnfd, vdu_id)
+
+ except (
+ DbException,
+ AttributeError,
+ IndexError,
+ KeyError,
+ ValueError,
+ ) as e:
+ raise type(e)(
+ "Ns update request could not be processed with error: {}.".format(e)
+ )
+
def _check_scale_ns_operation(self, indata, nsr):
vnfd = self._get_vnfd_from_vnf_member_index(
indata["scaleVnfData"]["scaleByStepData"]["member-vnf-index"], nsr["_id"]
)
)
+ def _check_heal_ns_operation(self, indata, nsr):
+ return
+
def _check_instantiate_ns_operation(self, indata, nsr, session):
vnf_member_index_to_vnfd = {} # map between vnf_member_index to vnf descriptor.
vim_accounts = []
"Invalid parameter member_vnf_index='{}' is not one of the "
"nsd:constituent-vnfd".format(member_vnf_index)
)
- vnfd = self.db.get_one("vnfds", {"_id": vnfr["vnfd-id"]}, fail_on_empty=False)
+
+ # Backwards compatibility: if there is no revision, get it from the one and only VNFD entry
+ if "revision" in vnfr:
+ vnfd_revision = vnfr["vnfd-id"] + ":" + str(vnfr["revision"])
+ vnfd = self.db.get_one(
+ "vnfds_revisions", {"_id": vnfd_revision}, fail_on_empty=False
+ )
+ else:
+ vnfd = self.db.get_one(
+ "vnfds", {"_id": vnfr["vnfd-id"]}, fail_on_empty=False
+ )
+
if not vnfd:
raise EngineException(
"vnfd id={} has been deleted!. Operation cannot be performed".format(
if in_vdu["id"] == vdu["id"]:
for volume in get_iterable(in_vdu.get("volume")):
for volumed in get_iterable(vdu.get("virtual-storage-desc")):
- if volumed["id"] == volume["name"]:
+ if volumed == volume["name"]:
break
else:
raise EngineException(
):
vdu_if_names.add(iface.get("name"))
- for in_iface in get_iterable(in_vdu["interface"]):
+ for in_iface in get_iterable(in_vdu.get("interface")):
if in_iface["name"] in vdu_if_names:
break
else:
ivld.get("id"): set()
for ivld in get_iterable(vnfd.get("int-virtual-link-desc"))
}
- for vdu in get_iterable(vnfd.get("vdu")):
- for cpd in get_iterable(vnfd.get("int-cpd")):
+ for vdu in vnfd.get("vdu", {}):
+ for cpd in vdu.get("int-cpd", {}):
if cpd.get("int-virtual-link-desc"):
vnfd_ivlds_cpds[cpd.get("int-virtual-link-desc")] = cpd.get("id")
return self.db.get_one("vim_accounts", db_filter)
except Exception:
raise EngineException(
- "Invalid vimAccountId='{}' not present for the project".format(
- vim_id
- )
+ "Invalid vimAccountId='{}' not present for the project".format(vim_id)
)
def _check_valid_wim_account(self, wim_account, wim_accounts, session):
if wim_account in wim_accounts:
return
try:
- db_filter = self._get_project_filter(session, write=False, show_all=True)
+ db_filter = self._get_project_filter(session)
db_filter["_id"] = wim_account
self.db.get_one("wim_accounts", db_filter)
except Exception:
# TODO check that this forcing is not incompatible with other forcing
return ifaces_forcing_vim_network
+ def _update_vnfrs_from_nsd(self, nsr):
+ step = "Getting vnf_profiles from nsd" # first step must be defined outside try
+ try:
+ nsr_id = nsr["_id"]
+ nsd = nsr["nsd"]
+
+ vnf_profiles = nsd.get("df", [{}])[0].get("vnf-profile", ())
+ vld_fixed_ip_connection_point_data = {}
+
+ step = "Getting ip-address info from vnf_profile if it exists"
+ for vnfp in vnf_profiles:
+ # Checking ip-address info from nsd.vnf_profile and storing
+ for vlc in vnfp.get("virtual-link-connectivity", ()):
+ for cpd in vlc.get("constituent-cpd-id", ()):
+ if cpd.get("ip-address"):
+ step = "Storing ip-address info"
+ vld_fixed_ip_connection_point_data.update(
+ {
+ vlc.get("virtual-link-profile-id")
+ + "."
+ + cpd.get("constituent-base-element-id"): {
+ "vnfd-connection-point-ref": cpd.get(
+ "constituent-cpd-id"
+ ),
+ "ip-address": cpd.get("ip-address"),
+ }
+ }
+ )
+
+ # Inserting ip address to vnfr
+ if len(vld_fixed_ip_connection_point_data) > 0:
+ step = "Getting vnfrs"
+ vnfrs = self.db.get_list("vnfrs", {"nsr-id-ref": nsr_id})
+ for item in vld_fixed_ip_connection_point_data.keys():
+ step = "Filtering vnfrs"
+ vnfr = next(
+ filter(
+ lambda vnfr: vnfr["member-vnf-index-ref"]
+ == item.split(".")[1],
+ vnfrs,
+ ),
+ None,
+ )
+ if vnfr:
+ vnfr_update = {}
+ for vdur_index, vdur in enumerate(vnfr["vdur"]):
+ for iface_index, iface in enumerate(vdur["interfaces"]):
+ step = "Looking for matched interface"
+ if (
+ iface.get("external-connection-point-ref")
+ == vld_fixed_ip_connection_point_data[item].get(
+ "vnfd-connection-point-ref"
+ )
+ and iface.get("ns-vld-id") == item.split(".")[0]
+ ):
+ vnfr_update_text = "vdur.{}.interfaces.{}".format(
+ vdur_index, iface_index
+ )
+ step = "Storing info in order to update vnfr"
+ vnfr_update[
+ vnfr_update_text + ".ip-address"
+ ] = increment_ip_mac(
+ vld_fixed_ip_connection_point_data[item].get(
+ "ip-address"
+ ),
+ vdur.get("count-index", 0),
+ )
+ vnfr_update[vnfr_update_text + ".fixed-ip"] = True
+
+ step = "updating vnfr at database"
+ self.db.set_one("vnfrs", {"_id": vnfr["_id"]}, vnfr_update)
+ except (
+ ValidationError,
+ EngineException,
+ DbException,
+ MsgException,
+ FsException,
+ ) as e:
+ raise type(e)("{} while '{}'".format(e, step), http_code=e.http_code)
+
def _update_vnfrs(self, session, rollback, nsr, indata):
# get vnfr
nsr_id = nsr["_id"]
"""
Creates a ns-lcm-opp content to be stored at database.
:param nsr_id: internal id of the instance
- :param operation: instantiate, terminate, scale, action, ...
+ :param operation: instantiate, terminate, scale, action, update ...
:param params: user parameters for the operation
:return: dictionary following SOL005 format
"""
:param session: contains "username", "admin", "force", "public", "project_id", "set_project"
:param indata: descriptor with the parameters of the operation. It must contains among others
nsInstanceId: _id of the nsr to perform the operation
- operation: it can be: instantiate, terminate, action, TODO: update, heal
+ operation: it can be: instantiate, terminate, action, update TODO: heal
:param kwargs: used to override the indata descriptor
:param headers: http request headers
:return: id of the nslcmops
return (
None,
None,
+ None,
) # a none in this case is used to indicate not instantiated. It can be removed
if operation != "instantiate":
raise EngineException(
HTTPStatus.CONFLICT,
)
self._check_ns_operation(session, nsr, operation, indata)
+ if indata.get("primitive_params"):
+ indata["primitive_params"] = json.dumps(indata["primitive_params"])
+ elif indata.get("additionalParamsForVnf"):
+ indata["additionalParamsForVnf"] = json.dumps(
+ indata["additionalParamsForVnf"]
+ )
if operation == "instantiate":
+ self._update_vnfrs_from_nsd(nsr)
self._update_vnfrs(session, rollback, nsr, indata)
-
+ if (operation == "update") and (indata["updateType"] == "CHANGE_VNFPKG"):
+ nsr_update = {}
+ vnfd_id = indata["changeVnfPackageData"]["vnfdId"]
+ vnfd = self.db.get_one("vnfds", {"_id": vnfd_id})
+ nsd = self.db.get_one("nsds", {"_id": nsr["nsd-id"]})
+ ns_request = nsr["instantiate_params"]
+ vnfr = self.db.get_one(
+ "vnfrs", {"_id": indata["changeVnfPackageData"]["vnfInstanceId"]}
+ )
+ latest_vnfd_revision = vnfd["_admin"].get("revision", 1)
+ vnfr_vnfd_revision = vnfr.get("revision", 1)
+ if latest_vnfd_revision != vnfr_vnfd_revision:
+ old_vnfd_id = vnfd_id + ":" + str(vnfr_vnfd_revision)
+ old_db_vnfd = self.db.get_one(
+ "vnfds_revisions", {"_id": old_vnfd_id}
+ )
+ old_sw_version = old_db_vnfd.get("software-version", "1.0")
+ new_sw_version = vnfd.get("software-version", "1.0")
+ if new_sw_version != old_sw_version:
+ vnf_index = vnfr["member-vnf-index-ref"]
+ for vdu in vnfd["vdu"]:
+ self.nsrtopic._add_shared_volumes_to_nsr(
+ vdu, vnfd, nsr, vnf_index, latest_vnfd_revision
+ )
+ self.nsrtopic._add_flavor_to_nsr(
+ vdu, vnfd, nsr, vnf_index, latest_vnfd_revision
+ )
+ sw_image_id = vdu.get("sw-image-desc")
+ if sw_image_id:
+ image_data = self.nsrtopic._get_image_data_from_vnfd(
+ vnfd, sw_image_id
+ )
+ self.nsrtopic._add_image_to_nsr(nsr, image_data)
+ for alt_image in vdu.get("alternative-sw-image-desc", ()):
+ image_data = self.nsrtopic._get_image_data_from_vnfd(
+ vnfd, alt_image
+ )
+ self.nsrtopic._add_image_to_nsr(nsr, image_data)
+ nsr_update["image"] = nsr["image"]
+ nsr_update["flavor"] = nsr["flavor"]
+ nsr_update["shared-volumes"] = nsr["shared-volumes"]
+ self.db.set_one("nsrs", {"_id": nsr["_id"]}, nsr_update)
+ ns_k8s_namespace = self.nsrtopic._get_ns_k8s_namespace(
+ nsd, ns_request, session
+ )
+ vnfr_descriptor = (
+ self.nsrtopic._create_vnfr_descriptor_from_vnfd(
+ nsd,
+ vnfd,
+ vnfd_id,
+ vnf_index,
+ nsr,
+ ns_request,
+ ns_k8s_namespace,
+ latest_vnfd_revision,
+ )
+ )
+ indata["newVdur"] = vnfr_descriptor["vdur"]
nslcmop_desc = self._create_nslcmop(nsInstanceId, operation, indata)
_id = nslcmop_desc["_id"]
+ nsName = nsr.get("name")
self.format_on_new(
nslcmop_desc, session["project_id"], make_public=session["public"]
)
rollback.append({"topic": "nslcmops", "_id": _id})
if not slice_object:
self.msg.write("ns", operation, nslcmop_desc)
- return _id, None
+ return _id, nsName, None
except ValidationError as e: # TODO remove try Except, it is captured at nbi.py
raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
# except DbException as e:
# raise EngineException("Cannot get ns_instance '{}': {}".format(e), HTTPStatus.NOT_FOUND)
+ def cancel(self, rollback, session, indata=None, kwargs=None, headers=None):
+ validate_input(indata, self.operation_schema["cancel"])
+ # Override descriptor with query string kwargs
+ self._update_input_with_kwargs(indata, kwargs, yaml_format=True)
+ nsLcmOpOccId = indata["nsLcmOpOccId"]
+ cancelMode = indata["cancelMode"]
+ # get nslcmop from nsLcmOpOccId
+ _filter = BaseTopic._get_project_filter(session)
+ _filter["_id"] = nsLcmOpOccId
+ nslcmop = self.db.get_one("nslcmops", _filter)
+ # Fail is this is not an ongoing nslcmop
+ if nslcmop.get("operationState") not in [
+ "STARTING",
+ "PROCESSING",
+ "ROLLING_BACK",
+ ]:
+ raise EngineException(
+ "Operation is not in STARTING, PROCESSING or ROLLING_BACK state",
+ http_code=HTTPStatus.CONFLICT,
+ )
+ nsInstanceId = nslcmop["nsInstanceId"]
+ update_dict = {
+ "isCancelPending": True,
+ "cancelMode": cancelMode,
+ }
+ self.db.set_one(
+ "nslcmops", q_filter=_filter, update_dict=update_dict, fail_on_empty=False
+ )
+ data = {
+ "_id": nsLcmOpOccId,
+ "nsInstanceId": nsInstanceId,
+ "cancelMode": cancelMode,
+ }
+ self.msg.write("nslcmops", "cancel", data)
+
def delete(self, session, _id, dry_run=False, not_send_msg=None):
raise EngineException(
"Method delete called directly", HTTPStatus.INTERNAL_SERVER_ERROR
additional_params[k] = "!!yaml " + safe_dump(v)
return additional_params
- def _check_descriptor_dependencies(self, session, descriptor):
- """
- Check that the dependent descriptors exist on a new descriptor or edition
- :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
- :param descriptor: descriptor to be inserted or edit
- :return: None or raises exception
- """
- if not descriptor.get("nst-ref"):
- return
- nstd_id = descriptor["nst-ref"]
- if not self.get_item_list(session, "nsts", {"id": nstd_id}):
- raise EngineException(
- "Descriptor error at nst-ref='{}' references a non exist nstd".format(
- nstd_id
- ),
- http_code=HTTPStatus.CONFLICT,
- )
-
def check_conflict_on_del(self, session, _id, db_content):
"""
Check that NSI is not instantiated
:return: the _id of nsi descriptor created at database
"""
+ step = "checking quotas" # first step must be defined outside try
try:
- step = "checking quotas"
self.check_quota(session)
step = ""
self.db.create("nsis", nsi_descriptor)
rollback.append({"topic": "nsis", "_id": nsi_id})
return nsi_id, None
- except Exception as e: # TODO remove try Except, it is captured at nbi.py
- self.logger.exception(
- "Exception {} at NsiTopic.new()".format(e), exc_info=True
- )
- raise EngineException("Error {}: {}".format(step, e))
except ValidationError as e:
raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
+ except Exception as e: # TODO remove try Except, it is captured at nbi.py
+ # self.logger.exception(
+ # "Exception {} at NsiTopic.new()".format(e), exc_info=True
+ # )
+ raise EngineException("Error {}: {}".format(step, e))
def edit(self, session, _id, indata=None, kwargs=None, content=None):
raise EngineException(
_filter = self._get_project_filter(session)
_filter["_id"] = netsliceInstanceId
nsir = self.db.get_one("nsis", _filter)
- logging_prefix = "nsi={} {} ".format(netsliceInstanceId, operation)
+ # logging_prefix = "nsi={} {} ".format(netsliceInstanceId, operation)
del _filter["_id"]
# initial checking
# Creating NS_LCM_OP with the flag slice_object=True to not trigger the service instantiation
# message via kafka bus
- nslcmop, _ = self.nsi_NsLcmOpTopic.new(
+ nslcmop, _, _ = self.nsi_NsLcmOpTopic.new(
rollback, session, indata_ns, None, headers, slice_object=True
)
nslcmops.append(nslcmop)
self.db.set_one("nsis", {"_id": nsir["_id"]}, _update)
except (DbException, EngineException) as e:
if e.http_code == HTTPStatus.NOT_FOUND:
- self.logger.info(
- logging_prefix
- + "skipping NS={} because not found".format(nsr_id)
- )
pass
else:
raise