Fix Bug 1011: Multiple interface referenced to a single connection point
[osm/NBI.git] / osm_nbi / descriptor_topics.py
index 95ced0d..bcd6a03 100644 (file)
@@ -20,6 +20,7 @@ import json
 from hashlib import md5
 from osm_common.dbbase import DbException, deep_update_rfc7396
 from http import HTTPStatus
+from time import time
 from osm_nbi.validation import ValidationError, pdu_new_schema, pdu_edit_schema
 from osm_nbi.base_topic import BaseTopic, EngineException, get_iterable
 from osm_im.vnfd import vnfd as vnfd_im
@@ -38,6 +39,27 @@ class DescriptorTopic(BaseTopic):
 
     def check_conflict_on_edit(self, session, final_content, edit_content, _id):
         super().check_conflict_on_edit(session, final_content, edit_content, _id)
+
+        def _check_unique_id_name(descriptor, position=""):
+            for desc_key, desc_item in descriptor.items():
+                if isinstance(desc_item, list) and desc_item:
+                    used_ids = []
+                    desc_item_id = None
+                    for index, list_item in enumerate(desc_item):
+                        if isinstance(list_item, dict):
+                            _check_unique_id_name(list_item, "{}.{}[{}]"
+                                                  .format(position, desc_key, index))
+                            # Base case
+                            if index == 0 and (list_item.get("id") or list_item.get("name")):
+                                desc_item_id = "id" if list_item.get("id") else "name"
+                            if desc_item_id and list_item.get(desc_item_id):
+                                if list_item[desc_item_id] in used_ids:
+                                    position = "{}.{}[{}]".format(position, desc_key, index)
+                                    raise EngineException("Error: identifier {} '{}' is not unique and repeats at '{}'"
+                                                          .format(desc_item_id, list_item[desc_item_id],
+                                                                  position), HTTPStatus.UNPROCESSABLE_ENTITY)
+                                used_ids.append(list_item[desc_item_id])
+        _check_unique_id_name(final_content)
         # 1. validate again with pyangbind
         # 1.1. remove internal keys
         internal_keys = {}
@@ -72,12 +94,13 @@ class DescriptorTopic(BaseTopic):
         content["_admin"]["operationalState"] = "DISABLED"
         content["_admin"]["usageState"] = "NOT_IN_USE"
 
-    def delete_extra(self, session, _id, db_content):
+    def delete_extra(self, session, _id, db_content, not_send_msg=None):
         """
         Deletes file system storage associated with the descriptor
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: server internal id
         :param db_content: The database content of the descriptor
+        :param not_send_msg: To not send message (False) or store content (list) instead
         :return: None if ok or raises EngineException with the problem
         """
         self.fs.file_delete(_id, ignore_non_exist=True)
@@ -139,6 +162,7 @@ class DescriptorTopic(BaseTopic):
             self.format_on_new(content, session["project_id"], make_public=session["public"])
             _id = self.db.create(self.topic, content)
             rollback.append({"topic": self.topic, "_id": _id})
+            self._send_msg("created", {"_id": _id})
             return _id, None
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
@@ -280,11 +304,12 @@ class DescriptorTopic(BaseTopic):
 
             deep_update_rfc7396(current_desc, indata)
             self.check_conflict_on_edit(session, current_desc, indata, _id=_id)
+            current_desc["_admin"]["modified"] = time()
             self.db.replace(self.topic, _id, current_desc)
             self.fs.dir_rename(temp_folder, _id)
 
             indata["_id"] = _id
-            self._send_msg("created", indata)
+            self._send_msg("edited", indata)
 
             # TODO if descriptor has changed because kwargs update content and remove cached zip
             # TODO if zip is not present creates one
@@ -484,8 +509,17 @@ class VnfdTopic(DescriptorTopic):
                                           http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
 
         for vdu in get_iterable(indata.get("vdu")):
+            icp_refs = []
+            ecp_refs = []
             for interface in get_iterable(vdu.get("interface")):
                 if interface.get("external-connection-point-ref"):
+                    if interface.get("external-connection-point-ref") in ecp_refs:
+                        raise EngineException("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{}' "
+                                              "is referenced by other interface"
+                                              .format(vdu["id"], interface["name"],
+                                                      interface["external-connection-point-ref"]),
+                                              http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+                    ecp_refs.append(interface.get("external-connection-point-ref"))
                     for cp in get_iterable(indata.get("connection-point")):
                         if cp["name"] == interface["external-connection-point-ref"]:
                             break
@@ -495,8 +529,14 @@ class VnfdTopic(DescriptorTopic):
                                               .format(vdu["id"], interface["name"],
                                                       interface["external-connection-point-ref"]),
                                               http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
-
                 elif interface.get("internal-connection-point-ref"):
+                    if interface.get("internal-connection-point-ref") in icp_refs:
+                        raise EngineException("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}' "
+                                              "is referenced by other interface"
+                                              .format(vdu["id"], interface["name"],
+                                                      interface["internal-connection-point-ref"]),
+                                              http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+                    icp_refs.append(interface.get("internal-connection-point-ref"))
                     for internal_cp in get_iterable(vdu.get("internal-connection-point")):
                         if interface["internal-connection-point-ref"] == internal_cp.get("id"):
                             break
@@ -703,6 +743,19 @@ class NsdTopic(DescriptorTopic):
                                           "does not match any constituent-vnfd:member-vnf-index"
                                           .format(vld["id"], vnfd_cp["member-vnf-index-ref"]),
                                           http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+        # Check VNFFGD
+        for fgd in get_iterable(indata.get("vnffgd")):
+            for cls in get_iterable(fgd.get("classifier")):
+                rspref = cls.get("rsp-id-ref")
+                for rsp in get_iterable(fgd.get("rsp")):
+                    rspid = rsp.get("id")
+                    if rspid and rspref and rspid == rspref:
+                        break
+                else:
+                    raise EngineException(
+                        "Error at vnffgd[id='{}']:classifier[id='{}']:rsp-id-ref '{}' does not match any rsp:id"
+                        .format(fgd["id"], cls["id"], rspref),
+                        http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
         return indata
 
     def _validate_input_edit(self, indata, force=False):
@@ -739,11 +792,6 @@ class NsdTopic(DescriptorTopic):
             for referenced_vnfd_cp in get_iterable(vld.get("vnfd-connection-point-ref")):
                 # look if this vnfd contains this connection point
                 vnfd = member_vnfd_index.get(referenced_vnfd_cp["member-vnf-index-ref"])
-                if not vnfd:
-                    raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}'] "
-                                          "does not match any constituent-vnfd:member-vnf-index"
-                                          .format(vld["id"], referenced_vnfd_cp["member-vnf-index-ref"]),
-                                          http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
                 for vnfd_cp in get_iterable(vnfd.get("connection-point")):
                     if referenced_vnfd_cp.get("vnfd-connection-point-ref") == vnfd_cp["name"]:
                         break