adding more system tests.
[osm/NBI.git] / osm_nbi / descriptor_topics.py
index fd3e3f4..e9339e0 100644 (file)
@@ -1,5 +1,18 @@
 # -*- coding: utf-8 -*-
 
+# 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.
+
 import tarfile
 import yaml
 import json
@@ -11,6 +24,7 @@ from validation import ValidationError, pdu_new_schema, pdu_edit_schema
 from base_topic import BaseTopic, EngineException, get_iterable
 from osm_im.vnfd import vnfd as vnfd_im
 from osm_im.nsd import nsd as nsd_im
+from osm_im.nst import nst as nst_im
 from pyangbind.lib.serialise import pybindJSONDecoder
 import pyangbind.lib.pybindJSON as pybindJSON
 
@@ -37,6 +51,8 @@ class DescriptorTopic(BaseTopic):
         for k, v in internal_keys.items():
             final_content[k] = v
 
+        if force:
+            return
         # 2. check that this id is not present
         if "id" in edit_content:
             _filter = self._get_project_filter(session, write=False, show_all=False)
@@ -52,7 +68,7 @@ class DescriptorTopic(BaseTopic):
         BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
         content["_admin"]["onboardingState"] = "CREATED"
         content["_admin"]["operationalState"] = "DISABLED"
-        content["_admin"]["usageSate"] = "NOT_IN_USE"
+        content["_admin"]["usageState"] = "NOT_IN_USE"
 
     def delete(self, session, _id, force=False, dry_run=False):
         """
@@ -140,7 +156,7 @@ class DescriptorTopic(BaseTopic):
         :param kwargs: user query string to override parameters. NOT USED
         :param headers:  http request headers
         :param force: to be more tolerant with validation
-        :return: True package has is completely uploaded or False if partial content has been uplodaed.
+        :return: True if package is completely uploaded or False if partial content has been uploded
             Raise exception on error
         """
         # Check that _id exists and it is valid
@@ -264,7 +280,7 @@ class DescriptorTopic(BaseTopic):
             if kwargs:
                 self._update_input_with_kwargs(indata, kwargs)
             # it will call overrides method at VnfdTopic or NsdTopic
-            indata = self._validate_input_new(indata, force=force)
+            # indata = self._validate_input_edit(indata, force=force)
 
             deep_update_rfc7396(current_desc, indata)
             self.check_conflict_on_edit(session, current_desc, indata, _id=_id, force=force)
@@ -363,6 +379,11 @@ class DescriptorTopic(BaseTopic):
                 pybindJSONDecoder.load_ietf_json({'nsd:nsd-catalog': {'nsd': [data]}}, None, None, obj=mynsd,
                                                  path_helper=True, skip_unknown=force)
                 out = pybindJSON.dumps(mynsd, mode="ietf")
+            elif item == "nsts":
+                mynst = nst_im()
+                pybindJSONDecoder.load_ietf_json({'nst': [data]}, None, None, obj=mynst,
+                                                 path_helper=True, skip_unknown=force)
+                out = pybindJSON.dumps(mynst, mode="ietf")
             else:
                 raise EngineException("Not possible to validate '{}' item".format(item),
                                       http_code=HTTPStatus.INTERNAL_SERVER_ERROR)
@@ -401,6 +422,23 @@ class VnfdTopic(DescriptorTopic):
             clean_indata = clean_indata['vnfd:vnfd'][0]
         return clean_indata
 
+    def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False):
+        super().check_conflict_on_edit(session, final_content, edit_content, _id, force=force)
+
+        # set type of vnfd
+        contains_pdu = False
+        contains_vdu = False
+        for vdu in get_iterable(final_content.get("vdu")):
+            if vdu.get("pdu-type"):
+                contains_pdu = True
+            else:
+                contains_vdu = True
+        if contains_pdu:
+            final_content["_admin"]["type"] = "hnfd" if contains_vdu else "pnfd"
+        elif contains_vdu:
+            final_content["_admin"]["type"] = "vnfd"
+        # if neither vud nor pdu do not fill type
+
     def check_conflict_on_del(self, session, _id, force=False):
         """
         Check that there is not any NSD that uses this VNFD. Only NSDs belonging to this project are considered. Note
@@ -600,35 +638,82 @@ class NsdTopic(DescriptorTopic):
 
     def _validate_input_new(self, indata, force=False):
         indata = self.pyangbind_validation("nsds", indata, force)
+        # Cross references validation in the descriptor
         # TODO validata that if contains cloud-init-file or charms, have artifacts _admin.storage."pkg-dir" is not none
+        for vld in get_iterable(indata.get("vld")):
+            for vnfd_cp in get_iterable(vld.get("vnfd-connection-point-ref")):
+                for constituent_vnfd in get_iterable(indata.get("constituent-vnfd")):
+                    if vnfd_cp["member-vnf-index-ref"] == constituent_vnfd["member-vnf-index"]:
+                        if vnfd_cp.get("vnfd-id-ref") and vnfd_cp["vnfd-id-ref"] != constituent_vnfd["vnfd-id-ref"]:
+                            raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[vnfd-id-ref='{}'] "
+                                                  "does not match constituent-vnfd[member-vnf-index='{}']:vnfd-id-ref"
+                                                  " '{}'".format(vld["id"], vnfd_cp["vnfd-id-ref"],
+                                                                 constituent_vnfd["member-vnf-index"],
+                                                                 constituent_vnfd["vnfd-id-ref"]),
+                                                  http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+                        break
+                else:
+                    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"], vnfd_cp["member-vnf-index-ref"]),
+                                          http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
         return indata
 
     def _validate_input_edit(self, indata, force=False):
         # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
         return indata
 
-    def _check_descriptor_dependencies(self, session, descriptor):
+    def _check_descriptor_dependencies(self, session, descriptor, force=False):
         """
-        Check that the dependent descriptors exist on a new descriptor or edition
+        Check that the dependent descriptors exist on a new descriptor or edition. Also checks references to vnfd
+        connection points are ok
         :param session: client session information
         :param descriptor: descriptor to be inserted or edit
+        :param force: if true skip dependencies checking
         :return: None or raises exception
         """
-        if not descriptor.get("constituent-vnfd"):
+        if force:
             return
-        for vnf in descriptor["constituent-vnfd"]:
-            vnfd_id = vnf["vnfd-id-ref"]
-            filter_q = self._get_project_filter(session, write=False, show_all=True)
-            filter_q["id"] = vnfd_id
-            if not self.db.get_list("vnfds", filter_q):
-                raise EngineException("Descriptor error at 'constituent-vnfd':'vnfd-id-ref'='{}' references a non "
-                                      "existing vnfd".format(vnfd_id), http_code=HTTPStatus.CONFLICT)
+        member_vnfd_index = {}
+        if descriptor.get("constituent-vnfd") and not force:
+            for vnf in descriptor["constituent-vnfd"]:
+                vnfd_id = vnf["vnfd-id-ref"]
+                filter_q = self._get_project_filter(session, write=False, show_all=True)
+                filter_q["id"] = vnfd_id
+                vnf_list = self.db.get_list("vnfds", filter_q)
+                if not vnf_list:
+                    raise EngineException("Descriptor error at 'constituent-vnfd':'vnfd-id-ref'='{}' references a non "
+                                          "existing vnfd".format(vnfd_id), http_code=HTTPStatus.CONFLICT)
+                # elif len(vnf_list) > 1:
+                #     raise EngineException("More than one vnfd found for id='{}'".format(vnfd_id),
+                #                           http_code=HTTPStatus.CONFLICT)
+                member_vnfd_index[vnf["member-vnf-index"]] = vnf_list[0]
+
+        # Cross references validation in the descriptor and vnfd connection point validation
+        for vld in get_iterable(descriptor.get("vld")):
+            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
+                else:
+                    raise EngineException(
+                        "Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}']:vnfd-"
+                        "connection-point-ref='{}' references a non existing conection-point:name inside vnfd '{}'"
+                        .format(vld["id"], referenced_vnfd_cp["member-vnf-index-ref"],
+                                referenced_vnfd_cp["vnfd-connection-point-ref"], vnfd["id"]),
+                        http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
 
     def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False):
         super().check_conflict_on_edit(session, final_content, edit_content, _id, force=force)
 
-        if not force:
-            self._check_descriptor_dependencies(session, final_content)
+        self._check_descriptor_dependencies(session, final_content, force)
 
     def check_conflict_on_del(self, session, _id, force=False):
         """
@@ -647,6 +732,85 @@ class NsdTopic(DescriptorTopic):
             raise EngineException("There is some NSR that depends on this NSD", http_code=HTTPStatus.CONFLICT)
 
 
+class NstTopic(DescriptorTopic):
+    topic = "nsts"
+    topic_msg = "nst"
+
+    def __init__(self, db, fs, msg):
+        DescriptorTopic.__init__(self, db, fs, msg)
+
+    @staticmethod
+    def _remove_envelop(indata=None):
+        if not indata:
+            return {}
+        clean_indata = indata
+
+        if clean_indata.get('nst'):
+            if not isinstance(clean_indata['nst'], list) or len(clean_indata['nst']) != 1:
+                raise EngineException("'nst' must be a list only one element")
+            clean_indata = clean_indata['nst'][0]
+        elif clean_indata.get('nst:nst'):
+            if not isinstance(clean_indata['nst:nst'], list) or len(clean_indata['nst:nst']) != 1:
+                raise EngineException("'nst:nst' must be a list only one element")
+            clean_indata = clean_indata['nst:nst'][0]
+        return clean_indata
+
+    def _validate_input_edit(self, indata, force=False):
+        # TODO validate with pyangbind, serialize
+        return indata
+
+    def _validate_input_new(self, indata, force=False):
+        indata = self.pyangbind_validation("nsts", indata, force)
+        return indata.copy()
+
+    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("netslice-subnet"):
+            return
+        for nsd in descriptor["netslice-subnet"]:
+            nsd_id = nsd["nsd-ref"]
+            filter_q = self._get_project_filter(session, write=False, show_all=True)
+            filter_q["id"] = nsd_id
+            if not self.db.get_list("nsds", filter_q):
+                raise EngineException("Descriptor error at 'netslice-subnet':'nsd-ref'='{}' references a non "
+                                      "existing nsd".format(nsd_id), http_code=HTTPStatus.CONFLICT)
+
+    def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False):
+        super().check_conflict_on_edit(session, final_content, edit_content, _id, force=force)
+
+        self._check_descriptor_dependencies(session, final_content)
+
+    def check_conflict_on_del(self, session, _id, force=False):
+        """
+        Check that there is not any NSIR that uses this NST. Only NSIRs belonging to this project are considered. Note
+        that NST can be public and be used by other projects.
+        :param session:
+        :param _id: nst internal id
+        :param force: Avoid this checking
+        :return: None or raises EngineException with the conflict
+        """
+        # TODO: Check this method
+        if force:
+            return
+        # Get Network Slice Template from Database
+        _filter = self._get_project_filter(session, write=False, show_all=False)
+        _filter["_id"] = _id
+        nst = self.db.get_one("nsts", _filter)
+        
+        # Search NSIs using NST via nst-ref
+        _filter = self._get_project_filter(session, write=False, show_all=False)
+        _filter["nst-ref"] = nst["id"]
+        nsis_list = self.db.get_list("nsis", _filter)
+        for nsi_item in nsis_list:
+            if nsi_item["_admin"].get("nsiState") != "TERMINATED":
+                raise EngineException("There is some NSIS that depends on this NST", http_code=HTTPStatus.CONFLICT)
+
+
 class PduTopic(BaseTopic):
     topic = "pdus"
     topic_msg = "pdu"
@@ -658,10 +822,10 @@ class PduTopic(BaseTopic):
 
     @staticmethod
     def format_on_new(content, project_id=None, make_public=False):
-        BaseTopic.format_on_new(content, project_id=None, make_public=make_public)
+        BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
         content["_admin"]["onboardingState"] = "CREATED"
-        content["_admin"]["operationalState"] = "DISABLED"
-        content["_admin"]["usageSate"] = "NOT_IN_USE"
+        content["_admin"]["operationalState"] = "ENABLED"
+        content["_admin"]["usageState"] = "NOT_IN_USE"
 
     def check_conflict_on_del(self, session, _id, force=False):
         if force: