X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=blobdiff_plain;f=osm_nbi%2Finstance_topics.py;h=4a5293d178355a7fa0e6017e611031478d900d8e;hp=b4ce2706a6abbc81e00fad9fc9997c73fdc0d14f;hb=HEAD;hpb=cee2ebfe3b4a0e4fe7a566eeb6e3f959649e1fce diff --git a/osm_nbi/instance_topics.py b/osm_nbi/instance_topics.py index b4ce270..695a8f8 100644 --- a/osm_nbi/instance_topics.py +++ b/osm_nbi/instance_topics.py @@ -26,7 +26,12 @@ from osm_nbi.validation import ( ns_terminate, ns_action, ns_scale, + ns_update, + ns_heal, nsi_instantiate, + ns_migrate, + ns_verticalscale, + nslcmop_cancel, ) from osm_nbi.base_topic import ( BaseTopic, @@ -34,6 +39,7 @@ from osm_nbi.base_topic import ( get_iterable, deep_get, increment_ip_mac, + update_descriptor_usage_state, ) from yaml import safe_dump from osm_common.dbbase import DbException @@ -56,24 +62,6 @@ class NsrTopic(BaseTopic): 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) @@ -165,9 +153,14 @@ class NsrTopic(BaseTopic): 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 """ @@ -251,6 +244,9 @@ class NsrTopic(BaseTopic): 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", []): # check that enough parameters are supplied for the initial-config-primitive @@ -265,10 +261,13 @@ class NsrTopic(BaseTopic): 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") @@ -283,6 +282,7 @@ class NsrTopic(BaseTopic): "", "", "", + "", ): continue if ( @@ -312,8 +312,8 @@ class NsrTopic(BaseTopic): 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" @@ -374,9 +374,13 @@ class NsrTopic(BaseTopic): ) 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) @@ -437,6 +441,83 @@ class NsrTopic(BaseTopic): 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( @@ -480,7 +561,12 @@ class NsrTopic(BaseTopic): "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"] @@ -512,79 +598,11 @@ class NsrTopic(BaseTopic): 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) @@ -597,13 +615,26 @@ class NsrTopic(BaseTopic): # Add Affinity or Anti-affinity group information to NSR vdu_profiles = vnfd.get("df", [[]])[0].get("vdu-profile", ()) - ag_prefix_name = "{}-{}".format(nsr_descriptor["name"][:16], vnf_profile.get("id")[:16]) + affinity_group_prefix_name = "{}-{}".format( + nsr_descriptor["name"][:16], vnf_profile.get("id")[:16] + ) for vdu_profile in vdu_profiles: - ag_data = {} - for ag in vdu_profile.get("affinity-or-anti-affinity-group", ()): - ag_data = self._get_affinity_or_anti_affinity_group_data_from_vnfd(vnfd, ag["id"]) - self._add_affinity_or_anti_affinity_group_to_nsr(nsr_descriptor, ag_data, ag_prefix_name) + 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( @@ -611,41 +642,63 @@ class NsrTopic(BaseTopic): ) 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, ag_id): + 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_or_anti_affinity_group = utils.find_in_list( - vnfd.get("df", [[]])[0].get("affinity-or-anti-affinity-group", ()), lambda ag: ag["id"] == ag_id + affinity_group = utils.find_in_list( + vnfd.get("df", [[]])[0].get("affinity-or-anti-affinity-group", ()), + lambda ag: ag["id"] == affinity_group_id, ) - ag_data = {} - if affinity_or_anti_affinity_group and affinity_or_anti_affinity_group.get("id"): - ag_data["ag-id"] = affinity_or_anti_affinity_group["id"] - if affinity_or_anti_affinity_group and affinity_or_anti_affinity_group.get("type"): - ag_data["type"] = affinity_or_anti_affinity_group["type"] - if affinity_or_anti_affinity_group and affinity_or_anti_affinity_group.get("scope"): - ag_data["scope"] = affinity_or_anti_affinity_group["scope"] - return ag_data - - def _add_affinity_or_anti_affinity_group_to_nsr(self, nsr_descriptor, ag_data, ag_prefix_name): + 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 """ - ag = next( + affinity_group = next( ( f for f in nsr_descriptor["affinity-or-anti-affinity-group"] - if all(f.get(k) == ag_data[k] for k in ag_data) + if all(f.get(k) == affinity_group_data[k] for k in affinity_group_data) ), None, ) - if not ag: - ag_data["id"] = str(len(nsr_descriptor["affinity-or-anti-affinity-group"])) - ag_data["name"] = "{}-{}-{}".format(ag_prefix_name, ag_data["ag-id"][:32], ag_data.get("id") or 0) - nsr_descriptor["affinity-or-anti-affinity-group"].append(ag_data) + 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( @@ -685,6 +738,7 @@ class NsrTopic(BaseTopic): nsr_descriptor, ns_request, ns_k8s_namespace, + revision=None, ): vnfr_id = str(uuid4()) nsr_id = nsr_descriptor["id"] @@ -715,7 +769,6 @@ class NsrTopic(BaseTopic): if "revision" in vnfd: vnfr_descriptor["revision"] = vnfd["revision"] - vnf_k8s_namespace = ns_k8s_namespace if vnf_params: if vnf_params.get("k8s-namespace"): @@ -827,7 +880,7 @@ class NsrTopic(BaseTopic): 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"] + lambda stg_desc: stg_desc["id"] in vdu["virtual-storage-desc"], ) except Exception: vdu_virtual_storage_descriptors = [] @@ -840,7 +893,7 @@ class NsrTopic(BaseTopic): "interfaces": [], "additionalParams": additional_params, "vdu-name": vdu["name"], - "virtual-storages": vdu_virtual_storage_descriptors + "virtual-storages": vdu_virtual_storage_descriptors, } if vdu_params and vdu_params.get("config-units"): vdur["config-units"] = vdu_params["config-units"] @@ -862,7 +915,10 @@ class NsrTopic(BaseTopic): 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 } @@ -932,7 +988,7 @@ class NsrTopic(BaseTopic): 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" ) @@ -986,7 +1042,10 @@ class NsrTopic(BaseTopic): 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, @@ -995,30 +1054,63 @@ class NsrTopic(BaseTopic): 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: - ags_vdu_profile = utils.find_in_list( + vdu_profile_affinity_group = utils.find_in_list( vnfd.get("df")[0]["vdu-profile"], lambda a_vdu: a_vdu["id"] == vdu["id"], ) except Exception: - ags_vdu_profile = None - - if ags_vdu_profile: - ags_ids = [] - for ag in ags_vdu_profile.get("affinity-or-anti-affinity-group", ()): - vdu_ag = utils.find_in_list( - ags_vdu_profile.get("affinity-or-anti-affinity-group", ()), - lambda ag_fp: ag_fp["id"] == ag["id"], + 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_ags_data = utils.find_in_list( + nsr_affinity_group = utils.find_in_list( nsr_descriptor["affinity-or-anti-affinity-group"], lambda nsr_ag: ( - nsr_ag.get("ag-id") == vdu_ag.get("id") + nsr_ag.get("ag-id") == vdu_affinity_group.get("id") + and nsr_ag.get("member-vnf-index") + == vnfr_descriptor.get("member-vnf-index-ref") ), ) - ags_ids.append(nsr_ags_data["id"]) - vdur["affinity-or-anti-affinity-group-id"] = ags_ids + # 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") @@ -1037,7 +1129,6 @@ class NsrTopic(BaseTopic): 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): @@ -1049,15 +1140,22 @@ class NsrTopic(BaseTopic): :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 @@ -1111,18 +1209,24 @@ class NsLcmOpTopic(BaseTopic): 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, + "verticalscale": ns_verticalscale, + "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 """ @@ -1130,6 +1234,10 @@ class NsLcmOpTopic(BaseTopic): 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) @@ -1222,6 +1330,94 @@ class NsLcmOpTopic(BaseTopic): ) ) + 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. + + 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: 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) + ) + + 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"] @@ -1242,6 +1438,9 @@ class NsLcmOpTopic(BaseTopic): ) ) + 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 = [] @@ -1293,12 +1492,16 @@ class NsLcmOpTopic(BaseTopic): "nsd:constituent-vnfd".format(member_vnf_index) ) - ## Backwards compatibility: if there is no revision, get it from the one and only VNFD entry + # 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) + 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) + vnfd = self.db.get_one( + "vnfds", {"_id": vnfr["vnfd-id"]}, fail_on_empty=False + ) if not vnfd: raise EngineException( @@ -1336,7 +1539,7 @@ class NsLcmOpTopic(BaseTopic): 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( @@ -1356,7 +1559,7 @@ class NsLcmOpTopic(BaseTopic): ): 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: @@ -1380,8 +1583,8 @@ class NsLcmOpTopic(BaseTopic): 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") @@ -1430,9 +1633,7 @@ class NsLcmOpTopic(BaseTopic): 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): @@ -1441,7 +1642,7 @@ class NsLcmOpTopic(BaseTopic): 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: @@ -1698,11 +1899,11 @@ class NsLcmOpTopic(BaseTopic): 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"] - step = "Getting vnf_profiles from nsd" vnf_profiles = nsd.get("df", [{}])[0].get("vnf-profile", ()) vld_fixed_ip_connection_point_data = {} @@ -1713,11 +1914,18 @@ class NsLcmOpTopic(BaseTopic): 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")}}) + 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: @@ -1725,17 +1933,25 @@ class NsLcmOpTopic(BaseTopic): 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) + 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] - + 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 @@ -1743,19 +1959,22 @@ class NsLcmOpTopic(BaseTopic): 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), ) + ] = 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, + ValidationError, + EngineException, + DbException, + MsgException, + FsException, ) as e: raise type(e)("{} while '{}'".format(e, step), http_code=e.http_code) @@ -1958,7 +2177,7 @@ class NsLcmOpTopic(BaseTopic): """ 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 """ @@ -2014,7 +2233,7 @@ class NsLcmOpTopic(BaseTopic): :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 @@ -2076,15 +2295,75 @@ class NsLcmOpTopic(BaseTopic): HTTPStatus.CONFLICT, ) self._check_ns_operation(session, nsr, operation, indata) - if (indata.get("primitive_params")): + if indata.get("primitive_params"): indata["primitive_params"] = json.dumps(indata["primitive_params"]) - elif (indata.get("additionalParamsForVnf")): - indata["additionalParamsForVnf"] = json.dumps(indata["additionalParamsForVnf"]) + 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"] + self.logger.info("nsr {}".format(nsr)) + 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"] self.format_on_new( @@ -2105,6 +2384,41 @@ class NsLcmOpTopic(BaseTopic): # 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 @@ -2157,24 +2471,6 @@ class NsiTopic(BaseTopic): 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 @@ -2260,8 +2556,8 @@ class NsiTopic(BaseTopic): :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 = "" @@ -2449,13 +2745,13 @@ class NsiTopic(BaseTopic): self.db.create("nsis", nsi_descriptor) rollback.append({"topic": "nsis", "_id": nsi_id}) return nsi_id, None + 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)) - except ValidationError as e: - raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) def edit(self, session, _id, indata=None, kwargs=None, content=None): raise EngineException(