Manual update of pip requirements 54/12254/1 master
authorbeierlm <mark.beierl@canonical.com>
Fri, 24 Jun 2022 17:40:58 +0000 (17:40 +0000)
committerbeierlm <mark.beierl@canonical.com>
Fri, 24 Jun 2022 17:40:58 +0000 (17:40 +0000)
Change-Id: I963e8f63e80e6b35f090b5c03a0ada223118e65e
Signed-off-by: beierlm <mark.beierl@canonical.com>
19 files changed:
Dockerfile.local
osm_nbi/descriptor_topics.py
osm_nbi/engine.py
osm_nbi/instance_topics.py
osm_nbi/nbi.py
osm_nbi/notifications.py
osm_nbi/osm_vnfm/vnf_instance_actions.py
osm_nbi/osm_vnfm/vnf_instances.py
osm_nbi/osm_vnfm/vnf_subscription.py [new file with mode: 0644]
osm_nbi/resources_to_operations.yml
osm_nbi/subscriptions.py
osm_nbi/tests/test_db_descriptors.py
osm_nbi/tests/test_descriptor_topics.py
osm_nbi/tests/test_instance_topics.py
osm_nbi/validation.py
requirements-dev.txt
requirements-test.txt
requirements.txt
tox.ini

index 2816d0a..04f2d2e 100644 (file)
@@ -16,7 +16,7 @@
 
 ########################################################################
 
-FROM ubuntu:18.04 as INSTALL
+FROM ubuntu:20.04 as INSTALL
 
 WORKDIR /build
 
@@ -46,13 +46,13 @@ RUN python3 -m build /build && \
     python3 -m pip install /build/dist/*.whl
 
 
-FROM ubuntu:18.04 as FINAL
+FROM ubuntu:20.04 as FINAL
 
 RUN DEBIAN_FRONTEND=noninteractive apt-get --yes update && \
     DEBIAN_FRONTEND=noninteractive apt-get --yes install python3-minimal
 
 COPY --from=INSTALL /usr/lib/python3/dist-packages /usr/lib/python3/dist-packages
-COPY --from=INSTALL /usr/local/lib/python3.6/dist-packages  /usr/local/lib/python3.6/dist-packages
+COPY --from=INSTALL /usr/local/lib/python3.8/dist-packages  /usr/local/lib/python3.8/dist-packages
 
 RUN mkdir -p /app/storage/kafka && mkdir -p /app/log
 
@@ -60,9 +60,9 @@ WORKDIR /app/osm_nbi
 
 EXPOSE 9999
 
-RUN cp -R /usr/local/lib/python3.6/dist-packages/osm_nbi/html_public /app/osm_nbi/html_public
-RUN cp /usr/local/lib/python3.6/dist-packages/osm_nbi/nbi.cfg /app/osm_nbi/
-RUN cp -R /usr/local/lib/python3.6/dist-packages/osm_nbi/http /app/osm_nbi/
+RUN cp -R /usr/local/lib/python3.8/dist-packages/osm_nbi/html_public /app/osm_nbi/html_public
+RUN cp /usr/local/lib/python3.8/dist-packages/osm_nbi/nbi.cfg /app/osm_nbi/
+RUN cp -R /usr/local/lib/python3.8/dist-packages/osm_nbi/http /app/osm_nbi/
 
 # Used for local storage
 VOLUME /app/storage
index 590380a..ddec65c 100644 (file)
@@ -268,6 +268,8 @@ class DescriptorTopic(BaseTopic):
         # TODO change to Content-Disposition filename https://tools.ietf.org/html/rfc6266
         file_pkg = None
         error_text = ""
+        fs_rollback = []
+
         try:
             if content_range_text:
                 content_range = (
@@ -296,9 +298,10 @@ class DescriptorTopic(BaseTopic):
             else:
                 self.fs.file_delete(proposed_revision_path, ignore_non_exist=True)
                 self.fs.mkdir(proposed_revision_path)
+                fs_rollback.append(proposed_revision_path)
 
             storage = self.fs.get_params()
-            storage["folder"] = _id
+            storage["folder"] = proposed_revision_path
 
             file_path = (proposed_revision_path, filename)
             if self.fs.file_exists(file_path, "file"):
@@ -464,17 +467,6 @@ class DescriptorTopic(BaseTopic):
                     self.fs.file_delete(proposed_revision_path, ignore_non_exist=True)
                     raise e
 
-            # Copy the revision to the active package name by its original id
-            shutil.rmtree(self.fs.path + current_revision_path, ignore_errors=True)
-            os.rename(self.fs.path + proposed_revision_path, self.fs.path + current_revision_path)
-            self.fs.file_delete(current_revision_path, ignore_non_exist=True)
-            self.fs.mkdir(current_revision_path)
-            self.fs.reverse_sync(from_path=current_revision_path)
-            shutil.rmtree(self.fs.path + _id)
-
-            current_desc["_admin"]["storage"] = storage
-            current_desc["_admin"]["onboardingState"] = "ONBOARDED"
-            current_desc["_admin"]["operationalState"] = "ENABLED"
 
             indata = self._remove_envelop(indata)
 
@@ -482,18 +474,33 @@ class DescriptorTopic(BaseTopic):
             if kwargs:
                 self._update_input_with_kwargs(indata, kwargs)
 
+            current_desc["_admin"]["storage"] = storage
+            current_desc["_admin"]["onboardingState"] = "ONBOARDED"
+            current_desc["_admin"]["operationalState"] = "ENABLED"
+            current_desc["_admin"]["modified"] = time()
+            current_desc["_admin"]["revision"] = revision
+
             deep_update_rfc7396(current_desc, indata)
             current_desc = self.check_conflict_on_edit(
                 session, current_desc, indata, _id=_id
             )
-            current_desc["_admin"]["modified"] = time()
-            current_desc["_admin"]["revision"] = revision
+
+            # Copy the revision to the active package name by its original id
+            shutil.rmtree(self.fs.path + current_revision_path, ignore_errors=True)
+            os.rename(self.fs.path + proposed_revision_path, self.fs.path + current_revision_path)
+            self.fs.file_delete(current_revision_path, ignore_non_exist=True)
+            self.fs.mkdir(current_revision_path)
+            self.fs.reverse_sync(from_path=current_revision_path)
+
+            shutil.rmtree(self.fs.path + _id)
+
             self.db.replace(self.topic, _id, current_desc)
 
             #  Store a copy of the package as a point in time revision
             revision_desc = dict(current_desc)
             revision_desc["_id"] = _id + ":" + str(revision_desc["_admin"]["revision"])
             self.db.create(self.topic + "_revisions", revision_desc)
+            fs_rollback = []
 
             indata["_id"] = _id
             self._send_msg("edited", indata)
@@ -525,6 +532,8 @@ class DescriptorTopic(BaseTopic):
         finally:
             if file_pkg:
                 file_pkg.close()
+            for file in fs_rollback:
+                self.fs.file_delete(file, ignore_non_exist=True)
 
     def get_file(self, session, _id, path=None, accept_header=None):
         """
index 7afaa22..37f1fb2 100644 (file)
@@ -55,6 +55,7 @@ from osm_nbi.instance_topics import (
 from osm_nbi.vnf_instance_topics import VnfInstances, VnfLcmOpTopic
 from osm_nbi.pmjobs_topics import PmJobsTopic
 from osm_nbi.subscription_topics import NslcmSubscriptionsTopic
+from osm_nbi.osm_vnfm.vnf_subscription import VnflcmSubscriptionsTopic
 from base64 import b64encode
 from os import urandom  # , path
 from threading import Lock
@@ -88,6 +89,7 @@ class Engine(object):
         "nslcm_subscriptions": NslcmSubscriptionsTopic,
         "vnf_instances": VnfInstances,
         "vnflcmops": VnfLcmOpTopic,
+        "vnflcm_subscriptions": VnflcmSubscriptionsTopic,
         # [NEW_TOPIC]: add an entry here
         # "pm_jobs": PmJobsTopic will be added manually because it needs other parameters
     }
index 74815ca..176f86d 100644 (file)
@@ -27,7 +27,10 @@ from osm_nbi.validation import (
     ns_action,
     ns_scale,
     ns_update,
+    ns_heal,
     nsi_instantiate,
+    ns_migrate,
+    ns_verticalscale,
 )
 from osm_nbi.base_topic import (
     BaseTopic,
@@ -438,6 +441,80 @@ class NsrTopic(BaseTopic):
 
         return ns_k8s_namespace
 
+    def _add_flavor_to_nsr(self, vdu, vnfd, nsr_descriptor):
+        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)
+
     def _create_nsr_descriptor_from_nsd(self, nsd, ns_request, nsr_id, session):
         now = time()
         additional_params, _ = self._format_additional_params(
@@ -482,6 +559,9 @@ class NsrTopic(BaseTopic):
             "image": [],
             "affinity-or-anti-affinity-group": [],
         }
+        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"]
@@ -513,79 +593,7 @@ 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)
-
+                    self._add_flavor_to_nsr(vdu, vnfd, nsr_descriptor)
                     sw_image_id = vdu.get("sw-image-desc")
                     if sw_image_id:
                         image_data = self._get_image_data_from_vnfd(vnfd, sw_image_id)
@@ -888,7 +896,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")
                     vdu_iface = {
                         x: iface[x] for x in iface_fields if iface.get(x) is not None
                     }
@@ -1157,17 +1168,21 @@ class NsLcmOpTopic(BaseTopic):
         "action": ns_action,
         "update": ns_update,
         "scale": ns_scale,
+        "heal": ns_heal,
         "terminate": ns_terminate,
+        "migrate": ns_migrate,
+        "verticalscale": ns_verticalscale,
     }
 
     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, update. TODO: heal
+        :param operation: it can be: instantiate, terminate, action, update, heal
         :param indata: descriptor with the parameters of the operation
         :return: None
         """
@@ -1177,6 +1192,8 @@ class NsLcmOpTopic(BaseTopic):
             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)
 
@@ -1276,6 +1293,9 @@ class NsLcmOpTopic(BaseTopic):
         - 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,
@@ -1336,6 +1356,14 @@ class NsLcmOpTopic(BaseTopic):
                         ),
                         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,
@@ -1368,6 +1396,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 = []
@@ -1462,7 +1493,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(
@@ -1482,7 +1513,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:
@@ -2210,7 +2241,46 @@ class NsLcmOpTopic(BaseTopic):
             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_flavor_to_nsr(vdu, vnfd, nsr)
+                            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"]
+                        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,
+                        )
+                        indata["newVdur"] = vnfr_descriptor["vdur"]
             nslcmop_desc = self._create_nslcmop(nsInstanceId, operation, indata)
             _id = nslcmop_desc["_id"]
             self.format_on_new(
index ff2f6de..d78379f 100644 (file)
@@ -85,8 +85,9 @@ URL: /osm                                                       GET     POST
                     terminate                                           O5
                     action                                              O
                     scale                                               O5
-                    heal                                                5
+                    migrate                                             O
                     update                                              05
+                    heal                                                O5
             /ns_lcm_op_occs                                     5       5
                 /<nsLcmOpOccId>                                 5                       5       5
                     TO BE COMPLETED                             5               5
@@ -171,7 +172,7 @@ query string:
         ADMIN: To act as an administrator or a different project
         PUBLIC: To get public descriptors or set a descriptor as public
         SET_PROJECT: To make a descriptor available for other project
-        
+
 Header field name      Reference       Example Descriptions
     Accept     IETF RFC 7231 [19]      application/json        Content-Types that are acceptable for the response.
     This header field shall be present if the response is expected to have a non-empty message body.
@@ -432,6 +433,10 @@ valid_url_methods = {
                 "<ID>": {
                     "METHODS": ("GET", "DELETE"),
                     "ROLE_PERMISSION": "ns_instances:id:",
+                    "heal": {
+                        "METHODS": ("POST",),
+                        "ROLE_PERMISSION": "ns_instances:id:heal:",
+                    },
                     "scale": {
                         "METHODS": ("POST",),
                         "ROLE_PERMISSION": "ns_instances:id:scale:",
@@ -444,6 +449,10 @@ valid_url_methods = {
                         "METHODS": ("POST",),
                         "ROLE_PERMISSION": "ns_instances:id:instantiate:",
                     },
+                    "migrate": {
+                        "METHODS": ("POST",),
+                        "ROLE_PERMISSION": "ns_instances:id:migrate:",
+                    },
                     "action": {
                         "METHODS": ("POST",),
                         "ROLE_PERMISSION": "ns_instances:id:action:",
@@ -452,6 +461,10 @@ valid_url_methods = {
                         "METHODS": ("POST",),
                         "ROLE_PERMISSION": "ns_instances:id:update:",
                     },
+                    "verticalscale": {
+                        "METHODS": ("POST",),
+                        "ROLE_PERMISSION": "ns_instances:id:verticalscale:"
+                           },
                 },
             },
             "ns_lcm_op_occs": {
@@ -505,6 +518,12 @@ valid_url_methods = {
                                         "ROLE_PERMISSION": "vnf_instances:opps:id:"
                                         },
                                },
+            "subscriptions": {"METHODS": ("GET", "POST"),
+                              "ROLE_PERMISSION": "vnflcm_subscriptions:",
+                              "<ID>": {"METHODS": ("GET", "DELETE"),
+                                       "ROLE_PERMISSION": "vnflcm_subscriptions:id:"
+                                       }
+                              },
         }
     },
     "nst": {
index 7b681a1..47a24ba 100644 (file)
@@ -348,11 +348,18 @@ class NsLcmNotification(NotificationBase):
         :param event_details: dict containing raw data of event occured.
         :return: List of interested subscribers for occurred event.
         """
+        notification_type = [
+            "NsLcmOperationOccurrenceNotification",
+            "NsChangeNotification",
+            "NsIdentifierCreationNotification",
+            "NsIdentifierDeletionNotification"
+        ]
         filter_q = {
             "identifier": [nsd_id, ns_instance_id],
             "operationStates": ["ANY"],
             "operationTypes": ["ANY"],
-        }
+            "notificationType": notification_type
+            }
         if op_state:
             filter_q["operationStates"].append(op_state)
         if command:
@@ -369,6 +376,121 @@ class NsLcmNotification(NotificationBase):
             return subscribers
 
 
+class VnfLcmNotification(NotificationBase):
+    # SOL003 response model for vnflcm notifications
+    response_models = {
+        "VnfLcmOperationOccurrenceNotification": {
+            "id",
+            "notificationType",
+            "subscriptionId",
+            "timeStamp",
+            "notificationStatus",
+            "operationState",
+            "vnfInstanceId",
+            "operation",
+            "isAutomaticInvocation",
+            "vnfLcmOpOccId",
+            "affectedVnfcs",
+            "affectedVirtualLinks",
+            "affectedExtLinkPorts",
+            "affectedVirtualStorages",
+            "changedInfo",
+            "changedExtConnectivity",
+            "modificationsTriggeredByVnfPkgChange",
+            "error",
+            "_links"
+        },
+        "VnfIdentifierCreationNotification": {
+            "id",
+            "notificationType",
+            "subscriptionId",
+            "timeStamp",
+            "vnfInstanceId",
+            "_links"
+        },
+        "VnfIdentifierDeletionNotification": {
+            "id",
+            "notificationType",
+            "subscriptionId",
+            "timeStamp",
+            "vnfInstanceId",
+            "_links"
+        },
+    }
+
+    def __init__(self, db) -> None:
+        """
+        Constructor of VnfLcmNotification class.
+        :param db: Database handler.
+        """
+        super().__init__(db)
+        self.subscriber_collection = "mapped_subscriptions"
+
+    def get_models(self) -> dict:
+        """
+        Returns the SOL003 model of notification class
+        :param None
+        :return: dict of SOL003 data model
+        """
+        return self.response_models
+
+    def _format_vnflcm_subscribers(self, subscribers: list, event_details: dict) -> list:
+        """
+        Formats the raw event details from kafka message and subscriber details.
+        :param subscribers: A list of subscribers whom the event needs to be notified.
+        :param event_details: A dict containing all meta data of event.
+        :return:
+        """
+        notification_id = str(uuid4())
+        event_timestamp = time.time()
+        event_operation = event_details["command"]
+        for subscriber in subscribers:
+            subscriber["id"] = notification_id
+            subscriber["timeStamp"] = event_timestamp
+            subscriber["subscriptionId"] = subscriber["reference"]
+            subscriber["operation"] = event_operation
+            del subscriber["reference"]
+            del subscriber["_id"]
+            subscriber.update(event_details["params"])
+        return subscribers
+
+    def get_subscribers(self, vnfd_id: str, vnf_instance_id: str, command: str, op_state: str,
+                        event_details: dict) -> list:
+        """
+        Queries database and returns list of subscribers.
+        :param vnfd_id: Vnfd id of a VNF whose lifecycle has changed. (instantiated, scaled, terminated. etc)
+        :param vnf_instance_id: Vnf instance id of a VNF whose lifecycle has changed.
+        :param command: the command for event.
+        :param op_state: the operation state of VNF.
+        :param event_details: dict containing raw data of event occurred.
+        :return: List of interested subscribers for occurred event.
+        """
+        notification_type = [
+            "VnfIdentifierCreationNotification",
+            "VnfLcmOperationOccurrenceNotification",
+            "VnfIdentifierDeletionNotification"
+        ]
+        filter_q = {
+            "identifier": [vnfd_id, vnf_instance_id],
+            "operationStates": ["ANY"],
+            "operationTypes": ["ANY"],
+            "notificationType": notification_type
+        }
+        if op_state:
+            filter_q["operationStates"].append(op_state)
+        if command:
+            filter_q["operationTypes"].append(command)
+        subscribers = []
+        try:
+            subscribers = self.db.get_list(self.subscriber_collection, filter_q)
+            subscribers = self._format_vnflcm_subscribers(subscribers, event_details)
+        except Exception as e:
+            error_text = type(e).__name__ + ": " + str(e)
+            self.logger.debug("Error getting vnflcm subscribers: {}".format(error_text))
+        finally:
+            return subscribers
+
+
 class NsdNotification(NotificationBase):
     def __init__(self, db):
         """
index 93c91c5..947f0b7 100644 (file)
@@ -133,6 +133,31 @@ class NewVnfLcmOp(BaseMethod):
             }
         return formatted_indata
 
+    def notify_operation(self, session, _id, lcm_operation, op_id):
+        """
+        Formats the operation message params and sends to kafka
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param _id: vnf instance id
+        :param lcm_operation: lcm operation type of a VNF (instantiate, scale, terminate)
+        :param op_id: lcm operation id of a VNF
+        :return: None
+        """
+        vnfInstanceId = _id
+        operation = lcm_operation
+        nslcmop_rec = self.nslcmoptopic.show(session, op_id)
+        operation_status = nslcmop_rec["operationState"]
+        vnfr = self.vnfrtopic.show(session, vnfInstanceId)
+        links = {"self": "/osm/vnflcm/v1/vnf_lcm_op_occs/" + op_id,
+                 "vnfInstance": "/osm/vnflcm/v1/vnf_instances/" + vnfInstanceId}
+        params = {"vnfdId": vnfr["vnfd-ref"],
+                  "vnfInstanceId": vnfInstanceId,
+                  "operationState": operation_status,
+                  "vnfLcmOpOccId": op_id,
+                  "_links": links
+                  }
+        self.msg.write("vnf", operation, params)
+        return None
+
     def action(self, rollback, session, indata=None, kwargs=None, headers=None):
         """
         Creates an new lcm operation.
@@ -149,6 +174,7 @@ class NewVnfLcmOp(BaseMethod):
         indata["vnfInstanceId"] = vnfr.get("nsr-id-ref")
         indata = self.__get_formatted_indata(session, indata)
         op_id, _ = self.nslcmoptopic.new(rollback, session, indata, kwargs, headers)
+        self.notify_operation(session, vnfInstanceId, lcm_operation, op_id)
         return op_id, _
 
 
index a41f6d5..a6a57fc 100644 (file)
@@ -224,6 +224,11 @@ class NewVnfInstance(BaseMethod):
         nsr_id, _ = self.__create_nsr(rollback, session, formatted_indata, kwargs, headers)
         nsr = self.nsrtopic.show(session, nsr_id)
         vnfr_id =  nsr['constituent-vnfr-ref'][0]
+        if vnfr_id:
+            links = {"vnfInstance": "/osm/vnflcm/v1/vnf_instances/" + vnfr_id}
+            indata["vnfInstanceId"] = vnfr_id
+            indata["_links"] = links
+            self.msg.write("vnf", "create", indata)
         return vnfr_id, None
 
     def action(self, rollback, session, indata=None, kwargs=None, headers=None):
@@ -305,5 +310,10 @@ class DeleteVnfInstance(BaseMethod):
         ns_id = vnfr.get("nsr-id-ref")
         nsr = self.nsrtopic.show(session, ns_id)
         nsd_to_del = nsr['nsd']['_id']
+        links = {"vnfInstance": "/osm/vnflcm/v1/vnf_instances/" + _id}
+        params = {"vnfdId": vnfr["vnfd-ref"],
+                  "vnfInstanceId": _id,
+                  "_links": links}
+        self.msg.write("vnf", "delete", params)
         self.nsrtopic.delete(session, ns_id, dry_run, not_send_msg)
         return self.nsdtopic.delete(session, nsd_to_del, dry_run, not_send_msg)
diff --git a/osm_nbi/osm_vnfm/vnf_subscription.py b/osm_nbi/osm_vnfm/vnf_subscription.py
new file mode 100644 (file)
index 0000000..5371a44
--- /dev/null
@@ -0,0 +1,65 @@
+# Copyright 2021 Selvi Jayaraman (Tata Elxsi)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+__author__ = "Selvi Jayaraman <selvi.j@tataelxsi.co.in>"
+
+from osm_nbi.subscription_topics import CommonSubscriptions
+from osm_nbi.validation import vnf_subscription
+
+class VnflcmSubscriptionsTopic(CommonSubscriptions):
+    schema_new = vnf_subscription
+    def _subscription_mapper(self, _id, data, table):
+        """
+        Performs data transformation on subscription request
+        :param _id: subscription reference id
+        :param data: data to be transformed
+        :param table: table in which transformed data are inserted
+        """
+        formatted_data = []
+        formed_data = {
+            "reference": data.get("_id"),
+            "CallbackUri": data.get("CallbackUri")
+        }
+        if data.get("authentication"):
+            formed_data.update({"authentication": data.get("authentication")})
+        if data.get("filter"):
+            if data["filter"].get("VnfInstanceSubscriptionFilter"):
+                key = list(data["filter"]["VnfInstanceSubscriptionFilter"].keys())[0]
+                identifier = data["filter"]["VnfInstanceSubscriptionFilter"][key]
+                formed_data.update({"identifier": identifier})
+            if data["filter"].get("notificationTypes"):
+                for elem in data["filter"].get("notificationTypes"):
+                    update_dict = formed_data.copy()
+                    update_dict["notificationType"] = elem
+                    if elem == "VnfIdentifierCreationNotification":
+                        update_dict["operationTypes"] = "CREATE"
+                        update_dict["operationStates"] = "ANY"
+                        formatted_data.append(update_dict)
+                    elif elem == "VnfIdentifierDeletionNotification":
+                        update_dict["operationTypes"] = "DELETE"
+                        update_dict["operationStates"] = "ANY"
+                        formatted_data.append(update_dict)
+                    elif elem == "VnfLcmOperationOccurrenceNotification":
+                        if "operationTypes" in data["filter"].keys():
+                            update_dict["operationTypes"] = data["filter"]["operationTypes"]
+                        else:
+                            update_dict["operationTypes"] = "ANY"
+                        if "operationStates" in data["filter"].keys():
+                            update_dict["operationStates"] = data["filter"]["operationStates"]
+                        else:
+                            update_dict["operationStates"] = "ANY"
+                        formatted_data.append(update_dict)
+        self.db.create_list(table, formatted_data)
+        return None
index 1f75a5b..3c5ba3d 100644 (file)
@@ -135,6 +135,8 @@ resources_to_operations:
 
   "POST /nslcm/v1/ns_instances/<nsInstanceId>/scale": "ns_instances:id:scale:post"
 
+  "POST /nslcm/v1/ns_instances/<nsInstanceId>/migrate": "ns_instances:id:migrate:post"
+
   "GET /nslcm/v1/ns_lcm_op_occs": "ns_instances:opps:get"
 
   "GET /nslcm/v1/ns_lcm_op_occs/<nsLcmOpOccId>": "ns_instances:opps:id:get"
index 6810ccd..1f172dd 100644 (file)
@@ -29,7 +29,7 @@ from osm_common import dbmongo, dbmemory, msglocal, msgkafka
 from osm_common.dbbase import DbException
 from osm_common.msgbase import MsgException
 from osm_nbi.engine import EngineException
-from osm_nbi.notifications import NsLcmNotification
+from osm_nbi.notifications import NsLcmNotification, VnfLcmNotification
 
 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 
@@ -70,6 +70,7 @@ class SubscriptionThread(threading.Thread):
             "method": "delete",
         }
         self.nslcm = None
+        self.vnflcm = None
 
     async def start_kafka(self):
         # timeout_wait_for_kafka = 3*60
@@ -84,6 +85,7 @@ class SubscriptionThread(threading.Thread):
                 )
                 await self.msg.aiowrite("ns", "echo", "dummy message", loop=self.loop)
                 await self.msg.aiowrite("nsi", "echo", "dummy message", loop=self.loop)
+                await self.msg.aiowrite("vnf", "echo", "dummy message", loop=self.loop)
                 if not kafka_working:
                     self.logger.critical("kafka is working again")
                     kafka_working = True
@@ -104,7 +106,7 @@ class SubscriptionThread(threading.Thread):
                     self.logger.debug("Starting non-admin subscription task")
                     self.aiomain_task = asyncio.ensure_future(
                         self.msg.aioread(
-                            ("ns", "nsi"),
+                            ("ns", "nsi", "vnf"),
                             loop=self.loop,
                             aiocallback=self._msg_callback,
                         ),
@@ -178,6 +180,7 @@ class SubscriptionThread(threading.Thread):
                         )
                     )
             self.nslcm = NsLcmNotification(self.db)
+            self.vnflcm = VnfLcmNotification(self.db)
         except (DbException, MsgException) as e:
             raise SubscriptionException(str(e), http_code=e.http_code)
 
@@ -272,6 +275,33 @@ class SubscriptionThread(threading.Thread):
                     self.logger.debug(
                         "Message can not be used for notification of nslcm"
                     )
+            elif topic == "vnf":
+                if isinstance(params, dict):
+                    vnfd_id = params["vnfdId"]
+                    vnf_instance_id = params["vnfInstanceId"]
+                    if command == "create" or command == "delete":
+                        op_state = command
+                    else:
+                        op_state = params["operationState"]
+                    event_details = {
+                            "topic": topic,
+                            "command": command.upper(),
+                            "params": params,
+                            }
+                    subscribers = self.vnflcm.get_subscribers(
+                            vnfd_id,
+                            vnf_instance_id,
+                            command.upper(),
+                            op_state,
+                            event_details
+                            )
+                    if subscribers:
+                        asyncio.ensure_future(
+                                self.vnflcm.send_notifications(
+                                    subscribers, loop=self.loop
+                                ),
+                                loop=self.loop
+                            )
             elif topic == "nsi":
                 if command == "terminated" and params["operationState"] in (
                     "COMPLETED",
index ea3cdd8..1914190 100644 (file)
@@ -501,10 +501,12 @@ db_vnfrs_text = """
             mgmt-vnf: true
             name: mgmtVM-eth0
             ns-vld-id: mgmt
+            position: 1
         -   ip-address: 192.168.54.2
             mac-address: fa:16:3e:6e:7e:78
             name: mgmtVM-eth1
             vnf-vld-id: internal
+            position: 2
         internal-connection-point:
         -   connection-point-id: mgmtVM-internal
             id: mgmtVM-internal
index 6b9b2c3..147f3e7 100755 (executable)
@@ -202,7 +202,7 @@ class Test_VnfdTopic(TestCase):
                 )
                 self.assertEqual(admin["usageState"], "NOT_IN_USE", "Wrong usage state")
                 storage = admin["storage"]
-                self.assertEqual(storage["folder"], did, "Wrong storage folder")
+                self.assertEqual(storage["folder"], did + ":1", "Wrong storage folder")
                 self.assertEqual(
                     storage["descriptor"], "package", "Wrong storage descriptor"
                 )
@@ -1406,7 +1406,7 @@ class Test_NsdTopic(TestCase):
                 )
                 self.assertEqual(admin["usageState"], "NOT_IN_USE", "Wrong usage state")
                 storage = admin["storage"]
-                self.assertEqual(storage["folder"], did, "Wrong storage folder")
+                self.assertEqual(storage["folder"], did + ":1", "Wrong storage folder")
                 self.assertEqual(
                     storage["descriptor"], "package", "Wrong storage descriptor"
                 )
index b05ee0c..2ad596d 100644 (file)
@@ -57,7 +57,6 @@ class TestNsLcmOpTopic(unittest.TestCase):
         self.db.create_list("vnfrs", yaml.load(db_vnfrs_text, Loader=yaml.Loader))
         self.db.create_list("nsrs", yaml.load(db_nsrs_text, Loader=yaml.Loader))
         self.db.create = Mock(return_value="created_id")
-        self.db.set_one = Mock(return_value={"updated": 1})
         self.nsd = self.db.get_list("nsds")[0]
         self.nsd_id = self.nsd["_id"]
         self.nsr = self.db.get_list("nsrs")[0]
@@ -68,6 +67,7 @@ class TestNsLcmOpTopic(unittest.TestCase):
         self.vim_id = self.vim["_id"]
 
     def test_create_instantiate(self):
+        self.db.set_one = Mock(return_value={"updated": 1})
         session = {
             "force": False,
             "admin": False,
@@ -228,6 +228,59 @@ class TestNsLcmOpTopic(unittest.TestCase):
                 "Engine exception bad http_code with {}".format(indata_copy),
             )
 
+    def test_update_remove_vnf(self):
+        vnfr_id = self.db.get_list("vnfrs")[0]["_id"]
+        session = {}
+        self.db.set_one(
+            "nsrs",
+            {"_id": self.nsr_id},
+            {"_admin.nsState": "INSTANTIATED"},
+        )
+        indata = {
+            "lcmOperationType": "update",
+            "updateType": "REMOVE_VNF",
+            "nsInstanceId": self.nsr_id,
+            "removeVnfInstanceId": vnfr_id
+        }
+
+        session = {
+            "force": False,
+            "admin": False,
+            "public": False,
+            "project_id": [self.nsr_project],
+            "method": "write",
+        }
+        rollback = []
+        headers = {}
+
+        nslcmop_id, _ = self.nslcmop_topic.new(
+            rollback, session, indata, kwargs=None, headers=headers
+        )
+
+        self.assertEqual(
+            self.db.create.call_count,
+            1,
+            "database create not called, or called more than once",
+        )
+        _call = self.db.create.call_args_list[0]
+        self.assertEqual(
+            _call[0][0], "nslcmops", "nslcmops entry must be created at database"
+        )
+        created_nslcmop = _call[0][1]
+        self.assertEqual(
+            self.nsr_id,
+            created_nslcmop["nsInstanceId"],
+            "mismatch between nsId '_id' in created nslcmop and database nsr",
+        )
+        self.assertTrue(
+            created_nslcmop["lcmOperationType"] == "update",
+            "Database record must contain 'lcmOperationType=update'",
+        )
+        self.assertTrue(
+            created_nslcmop["operationParams"]["updateType"] == "REMOVE_VNF",
+            "Database record must contain 'updateType=REMOVE_VNF'",
+        )
+
 
 class TestNsLcmOpTopicWithMock(unittest.TestCase):
     def setUp(self):
@@ -356,6 +409,20 @@ class TestNsLcmOpTopicWithMock(unittest.TestCase):
                 "VNF instance: 88d90b0c-faff-4b9f-bccd-017f33985984",
             )
 
+        with self.subTest(i=5, t="Ns update REMOVE_VNF request validated with no exception"):
+            test_vnfr = yaml.load(db_vnfrs_text, Loader=yaml.Loader)
+            test_vnfr[0]["revision"] = 2
+            test_nsr = yaml.load(db_nsrs_text, Loader=yaml.Loader)
+            self.db.create_list("vnfrs", test_vnfr)
+            self.db.create_list("nsrs", test_nsr)
+            nsrs = self.db.get_list("nsrs")[1]
+            indata = {
+                "updateType": "REMOVE_VNF",
+                "removeVnfInstanceId": "88d90b0c-faff-4b9f-bccd-017f33985984",
+                "nsInstanceId": "f48163a6-c807-47bc-9682-f72caef5af85",
+            }
+            with self.assertNotRaises(EngineException):
+                self.nslcmop_topic._check_ns_operation(session, nsrs, "update", indata)
 
 class TestNsrTopic(unittest.TestCase):
     def setUp(self):
@@ -464,6 +531,19 @@ class TestNsrTopic(unittest.TestCase):
         self.assertEqual(
             len(created_vnfrs), 2, "created a mismatch number of vnfr at database"
         )
+
+        self.assertEqual(
+            created_vnfrs[0]["vdur"][0]["interfaces"][0]["position"],
+            1,
+            "vdur first interface position does not match",
+        )
+
+        self.assertEqual(
+            created_vnfrs[0]["vdur"][0]["interfaces"][1]["position"],
+            2,
+            "vdur second interface position does not match",
+        )
+
         self.assertEqual(
             len(created_nsrs), 1, "Only one nsrs must be created at database"
         )
index 5c75522..6ed6fba 100644 (file)
@@ -449,7 +449,7 @@ ns_update = {
         "nsInstanceId": id_schema,
         "timeout_ns_update": integer1_schema,
         "updateType": {
-            "enum": ["CHANGE_VNFPKG", "REMOVE_VNF", "MODIFY_VNF_INFORMATION"]
+            "enum": ["CHANGE_VNFPKG", "REMOVE_VNF", "MODIFY_VNF_INFORMATION", "OPERATE_VNF"]
         },
         "modifyVnfInfoData": {
             "type": "object",
@@ -468,6 +468,24 @@ ns_update = {
             },
             "required": ["vnfInstanceId", "vnfdId"],
         },
+        "operateVnfData": {
+            "type": "object",
+            "properties": {
+                "vnfInstanceId": id_schema,
+                "changeStateTo": name_schema,
+                "additionalParam": {
+                    "type": "object",
+                    "properties": {
+                        "run-day1": bool_schema,
+                        "vdu_id": name_schema,
+                        "count-index": integer0_schema,
+                    },
+                    "required": ["vdu_id", "count-index"],
+                    "additionalProperties": False,
+                }
+            },
+            "required": ["vnfInstanceId", "changeStateTo"],
+        }
     },
     "required": ["updateType"],
     "additionalProperties": False,
@@ -492,6 +510,7 @@ ns_action = {  # TODO for the moment it is only contemplated the vnfd primitive
     "required": ["primitive", "primitive_params"],  # TODO add member_vnf_index
     "additionalProperties": False,
 }
+
 ns_scale = {  # TODO for the moment it is only VDU-scaling
     "title": "ns scale input schema",
     "$schema": "http://json-schema.org/draft-04/schema#",
@@ -526,6 +545,96 @@ ns_scale = {  # TODO for the moment it is only VDU-scaling
     "additionalProperties": False,
 }
 
+ns_migrate = {
+    "title": "ns migrate input schema",
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "type": "object",
+    "properties": {
+        "lcmOperationType": string_schema,
+        "nsInstanceId": id_schema,
+        "vnfInstanceId": id_schema,
+        "migrateToHost": string_schema,
+        "vdu": {
+            "type": "object",
+                "properties": {
+                    "vduId": name_schema,
+                    "vduCountIndex": integer0_schema,
+                },
+                "required": ["vduId"],
+                "additionalProperties": False,
+        },
+    },
+    "required": ["vnfInstanceId"],
+    "additionalProperties": False
+}
+
+ns_heal = {
+    "title": "ns heal input schema",
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "type": "object",
+    "properties": {
+        "lcmOperationType": string_schema,
+        "nsInstanceId": id_schema,
+        "timeout_ns_heal": integer1_schema,
+        "healVnfData": {
+            "type": "array",
+            "items": {
+                "type": "object",
+                "properties": {
+                    "vnfInstanceId": id_schema,
+                    "cause": description_schema,
+                    "additionalParams": {
+                        "type": "object",
+                        "properties": {
+                            "run-day1": bool_schema,
+                            "vdu": {
+                                "type": "array",
+                                "items": {
+                                    "type": "object",
+                                    "properties": {
+                                        "run-day1": bool_schema,
+                                        "vdu-id": name_schema,
+                                        "count-index": integer0_schema,
+                                    },
+                                    "required": ["vdu-id"],
+                                    "additionalProperties": False,
+                                },
+                            },
+                        },
+                        "additionalProperties": False,
+                    },
+                },
+                "required": ["vnfInstanceId"],
+                "additionalProperties": False,
+            },
+        },
+    },
+    "required": ["healVnfData"],
+    "additionalProperties": False,
+}
+
+ns_verticalscale = {
+    "title": "vertial scale input schema",
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "type": "object",
+    "properties": {
+        "lcmOperationType": string_schema,
+        "nsInstanceId": id_schema,
+        "vnfInstanceId": id_schema,
+        "migrateToHost": string_schema,
+        "vdu": {
+            "type": "object",
+                "properties": {
+                    "vduId": name_schema,
+                    "vduCountIndex": integer0_schema,
+                },
+                "required": ["vduId"],
+                "additionalProperties": False,
+        },
+    },
+    "required": ["vnfInstanceId"],
+    "additionalProperties": False
+}
 
 schema_version = {"type": "string", "enum": ["1.0"]}
 schema_type = {"type": "string"}
@@ -696,6 +805,18 @@ sdn_external_port_schema = {
 }
 
 # K8s Clusters
+k8scluster_deploy_method_schema = {
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "Deployment methods for K8s cluster",
+    "type": "object",
+    "properties": {
+        "helm-chart": {"type": "boolean"},
+        "juju-bundle": {"type": "boolean"},
+        "helm-chart-v3": {"type": "boolean"},
+    },
+    "additionalProperties": False,
+    "minProperties": 3,
+}
 k8scluster_nets_schema = {
     "title": "k8scluster nets input schema",
     "$schema": "http://json-schema.org/draft-04/schema#",
@@ -718,6 +839,7 @@ k8scluster_new_schema = {
         "vca_id": id_schema,
         "k8s_version": string_schema,
         "nets": k8scluster_nets_schema,
+        "deployment_methods": k8scluster_deploy_method_schema,
         "namespace": name_schema,
         "cni": nameshort_list_schema,
     },
@@ -1082,6 +1204,7 @@ nbi_new_input_schemas = {
     "ns_action": ns_action,
     "ns_scale": ns_scale,
     "ns_update": ns_update,
+    "ns_heal": ns_heal,
     "pdus": pdu_new_schema,
 }
 
@@ -1287,6 +1410,63 @@ subscription = {
     "required": ["CallbackUri"],
 }
 
+vnflcmsub_schema = {
+    "title": "vnflcmsubscription input schema",
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+    "properties": {
+        "VnfInstanceSubscriptionFilter": {
+            "type": "object",
+            "properties": {
+                "vnfdIds": {"type": "array"},
+                "vnfInstanceIds": {"type": "array"},
+            },
+        },
+        "notificationTypes": {
+            "type": "array",
+            "items": {
+                "enum": [
+                    "VnfIdentifierCreationNotification",
+                    "VnfLcmOperationOccurrenceNotification",
+                    "VnfIdentifierDeletionNotification"
+                    ]
+            }
+        },
+        "operationTypes": {
+            "type": "array",
+            "items": {
+                "enum": [
+                    "INSTANTIATE", "SCALE", "SCALE_TO_LEVEL", "CHANGE_FLAVOUR", "TERMINATE",
+                    "HEAL", "OPERATE", "CHANGE_EXT_CONN", "MODIFY_INFO", "CREATE_SNAPSHOT",
+                    "REVERT_TO_SNAPSHOT", "CHANGE_VNFPKG"
+                    ]
+            }
+        },
+        "operationStates": {
+            "type": "array",
+            "items": {
+                "enum": [
+                    "STARTING", "PROCESSING", "COMPLETED", "FAILED_TEMP", "FAILED",
+                    "ROLLING_BACK", "ROLLED_BACK"
+                    ]
+            }
+        }
+    },
+    "required": ["VnfInstanceSubscriptionFilter", "notificationTypes"]
+ }
+
+vnf_subscription = {
+    "title": "vnf subscription input schema",
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+    "properties": {
+        "filter": vnflcmsub_schema,
+        "CallbackUri": description_schema,
+        "authentication": authentication_schema
+    },
+    "required": ["filter", "CallbackUri"]
+}
+
 
 class ValidationError(Exception):
     def __init__(self, message, http_code=HTTPStatus.UNPROCESSABLE_ENTITY):
index 9a0dc90..b036158 100644 (file)
@@ -16,7 +16,7 @@
 
 aiokafka==0.7.2
     # via -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master
-bitarray==2.3.5
+bitarray==2.5.1
     # via
     #   -r https://osm.etsi.org/gitweb/?p=osm/IM.git;a=blob_plain;f=requirements.txt;hb=master
     #   pyangbind
@@ -30,7 +30,7 @@ kafka-python==2.0.2
     # via
     #   -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master
     #   aiokafka
-lxml==4.7.1
+lxml==4.9.0
     # via
     #   -r https://osm.etsi.org/gitweb/?p=osm/IM.git;a=blob_plain;f=requirements.txt;hb=master
     #   pyang
@@ -39,7 +39,7 @@ osm-common @ git+https://osm.etsi.org/gerrit/osm/common.git@master
     # via -r requirements-dev.in
 osm-im @ git+https://osm.etsi.org/gerrit/osm/IM.git@master
     # via -r requirements-dev.in
-pyang==2.5.2
+pyang==2.5.3
     # via
     #   -r https://osm.etsi.org/gitweb/?p=osm/IM.git;a=blob_plain;f=requirements.txt;hb=master
     #   pyangbind
@@ -53,7 +53,7 @@ pyyaml==5.4.1
     # via
     #   -r https://osm.etsi.org/gitweb/?p=osm/IM.git;a=blob_plain;f=requirements.txt;hb=master
     #   -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master
-regex==2021.11.10
+regex==2022.6.2
     # via
     #   -r https://osm.etsi.org/gitweb/?p=osm/IM.git;a=blob_plain;f=requirements.txt;hb=master
     #   pyangbind
index 143d7c8..fbbf879 100644 (file)
@@ -26,23 +26,23 @@ asynctest==0.13.0
     # via -r requirements-test.in
 attrs==21.4.0
     # via aiohttp
-certifi==2021.10.8
+certifi==2022.6.15
     # via requests
 chardet==3.0.4
     # via
     #   aiohttp
     #   requests
-coverage==6.3.2
+coverage==6.4.1
     # via
     #   -r requirements-test.in
     #   nose2
-deepdiff==5.7.0
+deepdiff==5.8.1
     # via -r requirements-test.in
 idna==2.10
     # via
     #   requests
     #   yarl
-lxml==4.8.0
+lxml==4.9.0
     # via pyang
 multidict==4.7.6
     # via
@@ -50,7 +50,7 @@ multidict==4.7.6
     #   yarl
 nose2==0.11.0
     # via -r requirements-test.in
-ordered-set==4.0.2
+ordered-set==4.1.0
     # via deepdiff
 pyang==2.5.3
     # via -r requirements-test.in
index ad4d82c..8b1f3b4 100644 (file)
@@ -22,7 +22,7 @@ attrs==21.4.0
     # via
     #   aiohttp
     #   jsonschema
-certifi==2021.10.8
+certifi==2022.6.15
     # via requests
 chardet==3.0.4
     # via aiohttp
@@ -34,113 +34,114 @@ cherrypy==18.6.1
     # via -r requirements.in
 debtcollector==2.5.0
     # via
-    #   oslo.config
-    #   oslo.utils
+    #   oslo-config
+    #   oslo-utils
     #   python-keystoneclient
-deepdiff==5.7.0
+deepdiff==5.8.1
     # via -r requirements.in
 idna==3.3
     # via
     #   requests
     #   yarl
-importlib-resources==5.6.0
+importlib-resources==5.8.0
     # via
-    #   jaraco.text
+    #   jaraco-text
     #   jsonschema
 iso8601==1.0.2
     # via
     #   keystoneauth1
-    #   oslo.utils
-jaraco.classes==3.2.1
-    # via jaraco.collections
-jaraco.collections==3.5.1
+    #   oslo-utils
+jaraco-classes==3.2.1
+    # via jaraco-collections
+jaraco-collections==3.5.1
     # via cherrypy
-jaraco.context==4.1.1
-    # via jaraco.text
-jaraco.functools==3.5.0
+jaraco-context==4.1.1
+    # via jaraco-text
+jaraco-functools==3.5.0
     # via
     #   cheroot
-    #   jaraco.text
+    #   jaraco-text
     #   tempora
-jaraco.text==3.7.0
-    # via jaraco.collections
-jsonschema==4.4.0
+jaraco-text==3.8.0
+    # via jaraco-collections
+jsonschema==4.6.0
     # via -r requirements.in
-keystoneauth1==4.5.0
+keystoneauth1==4.6.0
     # via python-keystoneclient
-more-itertools==8.12.0
+more-itertools==8.13.0
     # via
     #   cheroot
     #   cherrypy
-    #   jaraco.classes
-    #   jaraco.functools
-msgpack==1.0.3
-    # via oslo.serialization
+    #   jaraco-classes
+    #   jaraco-functools
+msgpack==1.0.4
+    # via oslo-serialization
 multidict==4.7.6
     # via
     #   aiohttp
     #   yarl
 netaddr==0.8.0
     # via
-    #   oslo.config
-    #   oslo.utils
+    #   oslo-config
+    #   oslo-utils
 netifaces==0.11.0
-    # via oslo.utils
-ordered-set==4.0.2
+    # via oslo-utils
+ordered-set==4.1.0
     # via deepdiff
 os-service-types==1.7.0
     # via keystoneauth1
-oslo.config==8.8.0
+oslo-config==8.8.0
     # via python-keystoneclient
-oslo.i18n==5.1.0
+oslo-i18n==5.1.0
     # via
-    #   oslo.config
-    #   oslo.utils
+    #   oslo-config
+    #   oslo-utils
     #   python-keystoneclient
-oslo.serialization==4.3.0
+oslo-serialization==4.3.0
     # via python-keystoneclient
-oslo.utils==4.12.2
+oslo-utils==6.0.0
     # via
-    #   oslo.serialization
+    #   oslo-serialization
     #   python-keystoneclient
 packaging==21.3
-    # via oslo.utils
-pbr==5.8.1
+    # via
+    #   oslo-utils
+    #   python-keystoneclient
+pbr==5.9.0
     # via
     #   keystoneauth1
     #   os-service-types
-    #   oslo.i18n
-    #   oslo.serialization
-    #   oslo.utils
+    #   oslo-i18n
+    #   oslo-serialization
     #   python-keystoneclient
     #   stevedore
 portend==3.1.0
     # via cherrypy
-pyparsing==3.0.7
+pyparsing==3.0.9
     # via
-    #   oslo.utils
+    #   oslo-utils
     #   packaging
 pyrsistent==0.18.1
     # via jsonschema
-python-keystoneclient==4.4.0
+python-keystoneclient==4.5.0
     # via -r requirements.in
 pytz==2022.1
     # via
-    #   oslo.serialization
-    #   oslo.utils
+    #   oslo-serialization
+    #   oslo-utils
     #   tempora
 pyyaml==5.4.1
     # via
     #   -r requirements.in
-    #   oslo.config
-requests==2.27.1
+    #   oslo-config
+requests==2.28.0
     # via
     #   -r requirements.in
     #   keystoneauth1
-    #   oslo.config
+    #   oslo-config
     #   python-keystoneclient
 rfc3986==2.0.0
-    # via oslo.config
+    # via oslo-config
 six==1.16.0
     # via
     #   cheroot
@@ -150,19 +151,19 @@ six==1.16.0
 stevedore==3.5.0
     # via
     #   keystoneauth1
-    #   oslo.config
+    #   oslo-config
     #   python-keystoneclient
-tacacs_plus==2.6
+tacacs-plus==2.6
     # via -r requirements.in
 tempora==5.0.1
     # via portend
 urllib3==1.26.9
     # via requests
-wrapt==1.14.0
+wrapt==1.14.1
     # via debtcollector
 yarl==1.7.2
     # via aiohttp
-zc.lockfile==2.0
+zc-lockfile==2.0
     # via cherrypy
 zipp==3.8.0
     # via importlib-resources
diff --git a/tox.ini b/tox.ini
index 7827a7e..929119a 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -81,7 +81,7 @@ commands =
 
 #######################################################################################
 [testenv:pip-compile]
-deps =  pip-tools==6.4.0
+deps =  pip-tools==6.6.2
 skip_install = true
 whitelist_externals = bash
         [