Fix age key generation to convert pubkey to string
[osm/NBI.git] / osm_nbi / descriptor_topics.py
index ca10c42..287dbda 100644 (file)
@@ -20,6 +20,7 @@ import copy
 import os
 import shutil
 import functools
+import re
 
 # import logging
 from deepdiff import DeepDiff
@@ -30,12 +31,17 @@ from time import time
 from uuid import uuid4
 from re import fullmatch
 from zipfile import ZipFile
+from urllib.parse import urlparse
 from osm_nbi.validation import (
     ValidationError,
     pdu_new_schema,
     pdu_edit_schema,
     validate_input,
     vnfpkgop_new_schema,
+    ns_config_template,
+    vnf_schema,
+    vld_schema,
+    additional_params_for_vnf,
 )
 from osm_nbi.base_topic import (
     BaseTopic,
@@ -51,10 +57,14 @@ from osm_nbi import utils
 
 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 
+valid_helm_chart_re = re.compile(
+    r"^[a-z0-9]([-a-z0-9]*[a-z0-9]/)?([a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"
+)
+
 
 class DescriptorTopic(BaseTopic):
     def __init__(self, db, fs, msg, auth):
-        BaseTopic.__init__(self, db, fs, msg, auth)
+        super().__init__(db, fs, msg, auth)
 
     def _validate_input_new(self, indata, storage_params, force=False):
         return indata
@@ -316,6 +326,7 @@ class DescriptorTopic(BaseTopic):
                     HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE,
                 )
             file_pkg = self.fs.file_open(file_path, "a+b")
+
             if isinstance(indata, dict):
                 indata_text = yaml.safe_dump(indata, indent=4, default_flow_style=False)
                 file_pkg.write(indata_text.encode(encoding="utf-8"))
@@ -676,7 +687,7 @@ class DescriptorTopic(BaseTopic):
         # to preserve current expected behaviour
         if "userDefinedData" in indata:
             data = indata.pop("userDefinedData")
-            if type(data) == dict:
+            if isinstance(data, dict):
                 indata["_admin"]["userDefinedData"] = data
             else:
                 raise EngineException(
@@ -807,9 +818,9 @@ class VnfdTopic(DescriptorTopic):
             return
 
         _filter = self._get_project_filter(session)
-
         # check vnfrs using this vnfd
         _filter["vnfd-id"] = _id
+
         if self.db.get_list("vnfrs", _filter):
             raise EngineException(
                 "There is at least one VNF instance using this descriptor",
@@ -819,6 +830,7 @@ class VnfdTopic(DescriptorTopic):
         # check NSD referencing this VNFD
         del _filter["vnfd-id"]
         _filter["vnfd-id"] = descriptor_id
+
         if self.db.get_list("nsds", _filter):
             raise EngineException(
                 "There is at least one NS package referencing this descriptor",
@@ -847,9 +859,32 @@ class VnfdTopic(DescriptorTopic):
         self.validate_internal_virtual_links(indata)
         self.validate_monitoring_params(indata)
         self.validate_scaling_group_descriptor(indata)
+        self.validate_healing_group_descriptor(indata)
+        self.validate_alarm_group_descriptor(indata)
+        self.validate_storage_compute_descriptor(indata)
+        self.validate_helm_chart(indata)
 
         return indata
 
+    @staticmethod
+    def validate_helm_chart(indata):
+        def is_url(url):
+            result = urlparse(url)
+            return all([result.scheme, result.netloc])
+
+        kdus = indata.get("kdu", [])
+        for kdu in kdus:
+            helm_chart_value = kdu.get("helm-chart")
+            if not helm_chart_value:
+                continue
+            if not (
+                valid_helm_chart_re.match(helm_chart_value) or is_url(helm_chart_value)
+            ):
+                raise EngineException(
+                    "helm-chart '{}' is not valid".format(helm_chart_value),
+                    http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+                )
+
     @staticmethod
     def validate_mgmt_interface_connection_point(indata):
         if not indata.get("vdu"):
@@ -1093,6 +1128,12 @@ class VnfdTopic(DescriptorTopic):
     @staticmethod
     def validate_scaling_group_descriptor(indata):
         all_monitoring_params = set()
+        all_vdu_ids = set()
+        for df in get_iterable(indata.get("df")):
+            for il in get_iterable(df.get("instantiation-level")):
+                for vl in get_iterable(il.get("vdu-level")):
+                    all_vdu_ids.add(vl.get("vdu-id"))
+
         for ivld in get_iterable(indata.get("int-virtual-link-desc")):
             for mp in get_iterable(ivld.get("monitoring-parameters")):
                 all_monitoring_params.add(mp.get("id"))
@@ -1105,6 +1146,26 @@ class VnfdTopic(DescriptorTopic):
             for mp in get_iterable(df.get("monitoring-parameter")):
                 all_monitoring_params.add(mp.get("id"))
 
+        for df in get_iterable(indata.get("df")):
+            for sa in get_iterable(df.get("scaling-aspect")):
+                for deltas in get_iterable(
+                    sa.get("aspect-delta-details").get("deltas")
+                ):
+                    for vds in get_iterable(deltas.get("vdu-delta")):
+                        sa_vdu_id = vds.get("id")
+                        if sa_vdu_id and sa_vdu_id not in all_vdu_ids:
+                            raise EngineException(
+                                "df[id='{}']:scaling-aspect[id='{}']:aspect-delta-details"
+                                "[delta='{}']: "
+                                "vdu-id='{}' not defined in vdu".format(
+                                    df["id"],
+                                    sa["id"],
+                                    deltas["id"],
+                                    sa_vdu_id,
+                                ),
+                                http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+                            )
+
         for df in get_iterable(indata.get("df")):
             for sa in get_iterable(df.get("scaling-aspect")):
                 for sp in get_iterable(sa.get("scaling-policy")):
@@ -1171,6 +1232,95 @@ class VnfdTopic(DescriptorTopic):
                                 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
                             )
 
+    @staticmethod
+    def validate_healing_group_descriptor(indata):
+        all_vdu_ids = set()
+        for df in get_iterable(indata.get("df")):
+            for il in get_iterable(df.get("instantiation-level")):
+                for vl in get_iterable(il.get("vdu-level")):
+                    all_vdu_ids.add(vl.get("vdu-id"))
+
+        for df in get_iterable(indata.get("df")):
+            for ha in get_iterable(df.get("healing-aspect")):
+                for hp in get_iterable(ha.get("healing-policy")):
+                    hp_monitoring_param = hp.get("vdu-id")
+                    if hp_monitoring_param and hp_monitoring_param not in all_vdu_ids:
+                        raise EngineException(
+                            "df[id='{}']:healing-aspect[id='{}']:healing-policy"
+                            "[name='{}']: "
+                            "vdu-id='{}' not defined in vdu".format(
+                                df["id"],
+                                ha["id"],
+                                hp["event-name"],
+                                hp_monitoring_param,
+                            ),
+                            http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+                        )
+
+    @staticmethod
+    def validate_alarm_group_descriptor(indata):
+        all_monitoring_params = set()
+        for ivld in get_iterable(indata.get("int-virtual-link-desc")):
+            for mp in get_iterable(ivld.get("monitoring-parameters")):
+                all_monitoring_params.add(mp.get("id"))
+
+        for vdu in get_iterable(indata.get("vdu")):
+            for mp in get_iterable(vdu.get("monitoring-parameter")):
+                all_monitoring_params.add(mp.get("id"))
+
+        for df in get_iterable(indata.get("df")):
+            for mp in get_iterable(df.get("monitoring-parameter")):
+                all_monitoring_params.add(mp.get("id"))
+
+        for vdus in get_iterable(indata.get("vdu")):
+            for alarms in get_iterable(vdus.get("alarm")):
+                alarm_monitoring_param = alarms.get("vnf-monitoring-param-ref")
+                if (
+                    alarm_monitoring_param
+                    and alarm_monitoring_param not in all_monitoring_params
+                ):
+                    raise EngineException(
+                        "vdu[id='{}']:alarm[id='{}']:"
+                        "vnf-monitoring-param-ref='{}' not defined in any monitoring-param".format(
+                            vdus["id"],
+                            alarms["alarm-id"],
+                            alarm_monitoring_param,
+                        ),
+                        http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+                    )
+
+    @staticmethod
+    def validate_storage_compute_descriptor(indata):
+        all_vsd_ids = set()
+        for vsd in get_iterable(indata.get("virtual-storage-desc")):
+            all_vsd_ids.add(vsd.get("id"))
+
+        all_vcd_ids = set()
+        for vcd in get_iterable(indata.get("virtual-compute-desc")):
+            all_vcd_ids.add(vcd.get("id"))
+
+        for vdus in get_iterable(indata.get("vdu")):
+            for vsd_ref in vdus.get("virtual-storage-desc"):
+                if vsd_ref and vsd_ref not in all_vsd_ids:
+                    raise EngineException(
+                        "vdu[virtual-storage-desc='{}']"
+                        "not defined in vnfd".format(
+                            vsd_ref,
+                        ),
+                        http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+                    )
+
+        for vdus in get_iterable(indata.get("vdu")):
+            vcd_ref = vdus.get("virtual-compute-desc")
+            if vcd_ref and vcd_ref not in all_vcd_ids:
+                raise EngineException(
+                    "vdu[virtual-compute-desc='{}']"
+                    "not defined in vnfd".format(
+                        vdus["virtual-compute-desc"],
+                    ),
+                    http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+                )
+
     def delete_extra(self, session, _id, db_content, not_send_msg=None):
         """
         Deletes associate file system storage (via super)
@@ -1375,7 +1525,7 @@ class NsdTopic(DescriptorTopic):
     topic_msg = "nsd"
 
     def __init__(self, db, fs, msg, auth):
-        DescriptorTopic.__init__(self, db, fs, msg, auth)
+        super().__init__(db, fs, msg, auth)
 
     def pyangbind_validation(self, item, data, force=False):
         if self._descriptor_data_is_in_old_format(data):
@@ -1441,6 +1591,8 @@ class NsdTopic(DescriptorTopic):
         # 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("virtual-link-desc")):
             self.validate_vld_mgmt_network_with_virtual_link_protocol_data(vld, indata)
+        for fg in get_iterable(indata.get("vnffgd")):
+            self.validate_vnffgd_data(fg, indata)
 
         self.validate_vnf_profiles_vnfd_id(indata)
 
@@ -1462,6 +1614,45 @@ class NsdTopic(DescriptorTopic):
                             http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
                         )
 
+    @staticmethod
+    def validate_vnffgd_data(fg, indata):
+        position_list = []
+        all_vnf_ids = set(get_iterable(fg.get("vnf-profile-id")))
+        for fgposition in get_iterable(fg.get("nfp-position-element")):
+            position_list.append(fgposition["id"])
+
+        for nfpd in get_iterable(fg.get("nfpd")):
+            nfp_position = []
+            for position in get_iterable(nfpd.get("position-desc-id")):
+                nfp_position = position.get("nfp-position-element-id")
+                if position == "nfp-position-element-id":
+                    nfp_position = position.get("nfp-position-element-id")
+                if nfp_position[0] not in position_list:
+                    raise EngineException(
+                        "Error at vnffgd nfpd[id='{}']:nfp-position-element-id='{}' "
+                        "does not match any nfp-position-element".format(
+                            nfpd["id"], nfp_position[0]
+                        ),
+                        http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+                    )
+
+                for cp in get_iterable(position.get("cp-profile-id")):
+                    for cpe in get_iterable(cp.get("constituent-profile-elements")):
+                        constituent_base_element_id = cpe.get(
+                            "constituent-base-element-id"
+                        )
+                        if (
+                            constituent_base_element_id
+                            and constituent_base_element_id not in all_vnf_ids
+                        ):
+                            raise EngineException(
+                                "Error at vnffgd constituent_profile[id='{}']:vnfd-id='{}' "
+                                "does not match any constituent-base-element-id".format(
+                                    cpe["id"], constituent_base_element_id
+                                ),
+                                http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+                            )
+
     @staticmethod
     def validate_vnf_profiles_vnfd_id(indata):
         all_vnfd_ids = set(get_iterable(indata.get("vnfd-id")))
@@ -1505,7 +1696,7 @@ class NsdTopic(DescriptorTopic):
         # to preserve current expected behaviour
         if "userDefinedData" in indata:
             data = indata.pop("userDefinedData")
-            if type(data) == dict:
+            if isinstance(data, dict):
                 indata["_admin"]["userDefinedData"] = data
             else:
                 raise EngineException(
@@ -2032,3 +2223,107 @@ class VnfPkgOpTopic(BaseTopic):
         rollback.append({"topic": self.topic, "_id": vnfpkgop_id})
         self.msg.write(self.topic_msg, operation, vnfpkgop_desc)
         return vnfpkgop_id, None
+
+
+class NsConfigTemplateTopic(DescriptorTopic):
+    topic = "ns_config_template"
+    topic_msg = "nsd"
+    schema_new = ns_config_template
+    instantiation_params = {
+        "vnf": vnf_schema,
+        "vld": vld_schema,
+        "additionalParamsForVnf": additional_params_for_vnf,
+    }
+
+    def __init__(self, db, fs, msg, auth):
+        super().__init__(db, fs, msg, auth)
+
+    def check_conflict_on_del(self, session, _id, db_content):
+        """
+        Check that there is not any NSR that uses this NS CONFIG TEMPLATE. Only NSRs belonging to this project are considered.
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param _id: ns config template internal id
+        :param db_content: The database content of the _id
+        :return: None or raises EngineException with the conflict
+        """
+        if session["force"]:
+            return
+        descriptor = db_content
+        descriptor_id = descriptor.get("nsdId")
+        if not descriptor_id:  # empty nsd not uploaded
+            return
+
+        # check NS CONFIG TEMPLATE used by NS
+        ns_config_template_id = _id
+
+        if self.db.get_list(
+            "nsrs", {"instantiate_params.nsConfigTemplateId": ns_config_template_id}
+        ):
+            raise EngineException(
+                "There is at least one NS instance using this template",
+                http_code=HTTPStatus.CONFLICT,
+            )
+
+    def check_unique_template_name(self, edit_content, _id, session):
+        """
+        Check whether the name of the template is unique or not
+        """
+
+        if edit_content.get("name"):
+            name = edit_content.get("name")
+            db_content = self.db.get_one(
+                "ns_config_template", {"name": name}, fail_on_empty=False
+            )
+            if db_content is not None:
+                if db_content.get("_id") == _id:
+                    if db_content.get("name") == name:
+                        return
+                elif db_content.get("_id") != _id:
+                    raise EngineException(
+                        "{} of the template already exist".format(name)
+                    )
+            else:
+                return
+
+    def check_conflict_on_edit(self, session, final_content, edit_content, _id):
+        """
+        Check the input data format
+        And the edit content data too.
+        """
+        final_content = super().check_conflict_on_edit(
+            session, final_content, edit_content, _id
+        )
+        db_content_id = self.db.get_one(
+            "ns_config_template", {"_id": _id}, fail_on_empty=False
+        )
+        if not (
+            db_content_id.get("name")
+            and db_content_id.get("nsdId")
+            and db_content_id.get("config")
+        ):
+            validate_input(edit_content, self.schema_new)
+
+        try:
+            for key, value in edit_content.items():
+                if key == "name":
+                    self.check_unique_template_name(edit_content, _id, session)
+                elif key == "nsdId":
+                    ns_config_template = self.db.get_one(
+                        "ns_config_template", {"_id": _id}, fail_on_empty=False
+                    )
+                    if not ns_config_template.get("nsdId"):
+                        pass
+                    else:
+                        raise EngineException("Nsd id cannot be edited")
+                elif key == "config":
+                    edit_content_param = edit_content.get("config")
+                    for key, value in edit_content_param.items():
+                        param = key
+                        param_content = value
+                        validate_input(param_content, self.instantiation_params[param])
+            return final_content
+        except Exception as e:
+            raise EngineException(
+                "Error in instantiation parameters validation: {}".format(str(e)),
+                http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+            )