Merge branch 'netslice' 04/6904/1
authortierno <alfonso.tiernosepulveda@telefonica.com>
Fri, 16 Nov 2018 15:34:49 +0000 (16:34 +0100)
committertierno <alfonso.tiernosepulveda@telefonica.com>
Fri, 16 Nov 2018 15:34:49 +0000 (16:34 +0100)
Change-Id: Ic9e13d09ca7aeb77afdae6e5ccfb25952acd993f
Signed-off-by: tierno <alfonso.tiernosepulveda@telefonica.com>
12 files changed:
Dockerfile.local
osm_nbi/admin_topics.py
osm_nbi/descriptor_topics.py
osm_nbi/html_out.py
osm_nbi/html_public/version
osm_nbi/instance_topics.py
osm_nbi/nbi.cfg
osm_nbi/nbi.py
osm_nbi/tests/clear-all.sh
osm_nbi/tests/test.py
osm_nbi/validation.py
setup.py

index 0d2a3d6..372e6a9 100644 (file)
@@ -56,7 +56,7 @@ ENV OSMNBI_DATABASE_HOST                        mongo
 ENV OSMNBI_DATABASE_PORT                        27017
 # ENV OSMNBI_DATABASE_USER                      xxx
 # ENV OSMNBI_DATABASE_PASSWORD                  xxx
-# ENV OSMNBI_DATABASE_MASTERPASSWORD            xxx
+# ENV OSMNBI_DATABASE_COMMONKEY                 xxx
 # web
 ENV OSMNBI_STATIC_DIR                           /app/osm_nbi/html_public
 # logs
@@ -67,7 +67,7 @@ ENV OSMNBI_MESSAGE_DRIVER                       kafka
 ENV OSMNBI_MESSAGE_HOST                         kafka
 ENV OSMNBI_MESSAGE_PORT                         9092
 # logs
-ENV OSMNBI_LOG_FILE                             /app/log/nbi.log
+ENV OSMNBI_LOG_FILE                             /app/log/nbi.log
 ENV OSMNBI_LOG_LEVEL                            DEBUG
 # authentication
 ENV OSMNBI_AUTHENTICATION_BACKEND               internal
index 3b5da53..091ac88 100644 (file)
@@ -128,6 +128,7 @@ class VimAccountTopic(BaseTopic):
     topic_msg = "vim_account"
     schema_new = vim_account_new_schema
     schema_edit = vim_account_edit_schema
+    vim_config_encrypted = ("admin_password", "nsx_password", "vcenter_password")
 
     def __init__(self, db, fs, msg):
         BaseTopic.__init__(self, db, fs, msg)
@@ -136,12 +137,35 @@ class VimAccountTopic(BaseTopic):
         self.check_unique_name(session, indata["name"], _id=None)
 
     def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False):
-        if edit_content.get("name"):
+        if not force and edit_content.get("name"):
             self.check_unique_name(session, edit_content["name"], _id=_id)
 
-    @staticmethod
-    def format_on_new(content, project_id=None, make_public=False):
-        BaseTopic.format_on_new(content, project_id=project_id, make_public=False)
+        # encrypt passwords
+        schema_version = final_content.get("schema_version")
+        if schema_version:
+            if edit_content.get("vim_password"):
+                final_content["vim_password"] = self.db.encrypt(edit_content["vim_password"],
+                                                                schema_version=schema_version, salt=_id)
+            if edit_content.get("config"):
+                for p in self.vim_config_encrypted:
+                    if edit_content["config"].get(p):
+                        final_content["config"][p] = self.db.encrypt(edit_content["config"][p],
+                                                                     schema_version=schema_version, salt=_id)
+
+    def format_on_new(self, content, project_id=None, make_public=False):
+        BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
+        content["schema_version"] = schema_version = "1.1"
+
+        # encrypt passwords
+        if content.get("vim_password"):
+            content["vim_password"] = self.db.encrypt(content["vim_password"], schema_version=schema_version,
+                                                      salt=content["_id"])
+        if content.get("config"):
+            for p in self.vim_config_encrypted:
+                if content["config"].get(p):
+                    content["config"][p] = self.db.encrypt(content["config"][p], schema_version=schema_version,
+                                                           salt=content["_id"])
+
         content["_admin"]["operationalState"] = "PROCESSING"
 
     def delete(self, session, _id, force=False, dry_run=False):
@@ -176,12 +200,23 @@ class SdnTopic(BaseTopic):
         self.check_unique_name(session, indata["name"], _id=None)
 
     def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False):
-        if edit_content.get("name"):
+        if not force and edit_content.get("name"):
             self.check_unique_name(session, edit_content["name"], _id=_id)
 
-    @staticmethod
-    def format_on_new(content, project_id=None, make_public=False):
-        BaseTopic.format_on_new(content, project_id=project_id, make_public=False)
+        # encrypt passwords
+        schema_version = final_content.get("schema_version")
+        if schema_version and edit_content.get("password"):
+            final_content["password"] = self.db.encrypt(edit_content["password"], schema_version=schema_version,
+                                                        salt=_id)
+
+    def format_on_new(self, content, project_id=None, make_public=False):
+        BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
+        content["schema_version"] = schema_version = "1.1"
+        # encrypt passwords
+        if content.get("password"):
+            content["password"] = self.db.encrypt(content["password"], schema_version=schema_version,
+                                                  salt=content["_id"])
+
         content["_admin"]["operationalState"] = "PROCESSING"
 
     def delete(self, session, _id, force=False, dry_run=False):
index 3ace489..6285e38 100644 (file)
@@ -23,17 +23,29 @@ class DescriptorTopic(BaseTopic):
         BaseTopic.__init__(self, db, fs, msg)
 
     def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False):
-        # check that this id is not present
-        _filter = {"id": final_content["id"]}
-        if _id:
+        # 1. validate again with pyangbind
+        # 1.1. remove internal keys
+        internal_keys = {}
+        for k in ("_id", "_admin"):
+            if k in final_content:
+                internal_keys[k] = final_content.pop(k)
+        serialized = self._validate_input_new(final_content, force)
+        # 1.2. modify final_content with a serialized version
+        final_content.clear()
+        final_content.update(serialized)
+        # 1.3. restore internal keys
+        for k, v in internal_keys.items():
+            final_content[k] = v
+
+        # 2. check that this id is not present
+        if "id" in edit_content:
+            _filter = self._get_project_filter(session, write=False, show_all=False)
+            _filter["id"] = final_content["id"]
             _filter["_id.neq"] = _id
-
-        _filter.update(self._get_project_filter(session, write=False, show_all=False))
-        if self.db.get_one(self.topic, _filter, fail_on_empty=False):
-            raise EngineException("{} with id '{}' already exists for this project".format(self.topic[:-1],
-                                                                                           final_content["id"]),
-                                  HTTPStatus.CONFLICT)
-        # TODO validate with pyangbind. Load and dumps to convert data types
+            if self.db.get_one(self.topic, _filter, fail_on_empty=False):
+                raise EngineException("{} with id '{}' already exists for this project".format(self.topic[:-1],
+                                                                                               final_content["id"]),
+                                      HTTPStatus.CONFLICT)
 
     @staticmethod
     def format_on_new(content, project_id=None, make_public=False):
@@ -389,6 +401,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
@@ -418,20 +447,20 @@ class VnfdTopic(DescriptorTopic):
             raise EngineException("There is soame NSD that depends on this VNFD", http_code=HTTPStatus.CONFLICT)
 
     def _validate_input_new(self, indata, force=False):
-        # TODO validate with pyangbind, serialize
         indata = self.pyangbind_validation("vnfds", indata, force)
         # Cross references validation in the descriptor
-        if not indata.get("mgmt-interface"):
-            raise EngineException("'mgmt-interface' is a mandatory field and it is not defined",
-                                  http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
-        if indata["mgmt-interface"].get("cp"):
-            for cp in get_iterable(indata.get("connection-point")):
-                if cp["name"] == indata["mgmt-interface"]["cp"]:
-                    break
-            else:
-                raise EngineException("mgmt-interface:cp='{}' must match an existing connection-point"
-                                      .format(indata["mgmt-interface"]["cp"]),
+        if indata.get("vdu"):
+            if not indata.get("mgmt-interface"):
+                raise EngineException("'mgmt-interface' is a mandatory field and it is not defined",
                                       http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+            if indata["mgmt-interface"].get("cp"):
+                for cp in get_iterable(indata.get("connection-point")):
+                    if cp["name"] == indata["mgmt-interface"]["cp"]:
+                        break
+                else:
+                    raise EngineException("mgmt-interface:cp='{}' must match an existing connection-point"
+                                          .format(indata["mgmt-interface"]["cp"]),
+                                          http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
 
         for vdu in get_iterable(indata.get("vdu")):
             for interface in get_iterable(vdu.get("interface")):
@@ -551,10 +580,11 @@ class VnfdTopic(DescriptorTopic):
                                           "vnf-configuration:config-primitive:name"
                                           .format(sgd["name"], sca["vnf-config-primitive-name-ref"]),
                                           http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+        # TODO validata that if contains cloud-init-file or charms, have artifacts _admin.storage."pkg-dir" is not none
         return indata
 
     def _validate_input_edit(self, indata, force=False):
-        # TODO validate with pyangbind, serialize
+        # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
         return indata
 
 
@@ -586,13 +616,12 @@ class NsdTopic(DescriptorTopic):
         return clean_indata
 
     def _validate_input_new(self, indata, force=False):
-
-        # TODO validate with pyangbind, serialize
         indata = self.pyangbind_validation("nsds", indata, force)
+        # TODO validata that if contains cloud-init-file or charms, have artifacts _admin.storage."pkg-dir" is not none
         return indata
 
     def _validate_input_edit(self, indata, force=False):
-        # TODO validate with pyangbind, serialize
+        # 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):
@@ -615,7 +644,8 @@ class NsdTopic(DescriptorTopic):
     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)
+        if not force:
+            self._check_descriptor_dependencies(session, final_content)
 
     def check_conflict_on_del(self, session, _id, force=False):
         """
@@ -718,9 +748,9 @@ 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"]["operationalState"] = "ENABLED"
         content["_admin"]["usageState"] = "NOT_IN_USE"
 
     def check_conflict_on_del(self, session, _id, force=False):
index 8d3baab..6b596d7 100644 (file)
@@ -21,6 +21,7 @@ html_start = """
       <a href="https://osm.etsi.org"> <img src="/osm/static/OSM-logo.png" height="42" width="100"
         style="vertical-align:middle"> </a>
       <a>( {} )</a>
+      <a href="/osm/pdu/v1/pdu_descriptors">PDUs </a>
       <a href="/osm/vnfpkgm/v1/vnf_packages">VNFDs </a>
       <a href="/osm/nsd/v1/ns_descriptors">NSDs </a>
       <a href="/osm/nslcm/v1/ns_instances">NSs </a>
index 23fb4a2..fbec8cf 100644 (file)
@@ -1,2 +1,2 @@
-0.1.24
-2018-11-02
+0.1.28
+2018-11-16
index 41187dc..1301985 100644 (file)
@@ -126,13 +126,19 @@ class NsrTopic(BaseTopic):
                 "id": nsr_id,
                 "_id": nsr_id,
                 # "input-parameter": xpath, value,
-                "ssh-authorized-key": ns_request.get("key-pair-ref"),
+                "ssh-authorized-key": ns_request.get("key-pair-ref"),  # TODO remove
             }
             ns_request["nsr_id"] = nsr_id
+            # Create vld
+            if nsd.get("vld"):
+                nsr_descriptor["vld"] = []
+                for nsd_vld in nsd.get("vld"):
+                    nsr_descriptor["vld"].append(
+                        {key: nsd_vld[key] for key in ("id", "vim-network-name") if key in nsd_vld})
 
             # Create VNFR
             needed_vnfds = {}
-            for member_vnf in nsd["constituent-vnfd"]:
+            for member_vnf in nsd.get("constituent-vnfd", ()):
                 vnfd_id = member_vnf["vnfd-id-ref"]
                 step = "getting vnfd id='{}' constituent-vnfd='{}' from database".format(
                     member_vnf["vnfd-id-ref"], member_vnf["member-vnf-index"])
@@ -160,6 +166,15 @@ class NsrTopic(BaseTopic):
                     "connection-point": [],
                     "ip-address": None,  # mgmt-interface filled by LCM
                 }
+
+                # Create vld
+                if vnfd.get("internal-vld"):
+                    vnfr_descriptor["vld"] = []
+                    for vnfd_vld in vnfd.get("internal-vld"):
+                        vnfr_descriptor["vld"].append(
+                            {key: vnfd_vld[key] for key in ("id", "vim-network-name") if key in vnfd_vld})
+
+                vnfd_mgmt_cp = vnfd["mgmt-interface"].get("cp")
                 for cp in vnfd.get("connection-point", ()):
                     vnf_cp = {
                         "name": cp["name"],
@@ -169,7 +184,7 @@ class NsrTopic(BaseTopic):
                         # vim-id  # TODO it would be nice having a vim port id
                     }
                     vnfr_descriptor["connection-point"].append(vnf_cp)
-                for vdu in vnfd["vdu"]:
+                for vdu in vnfd.get("vdu", ()):
                     vdur = {
                         "vdu-id-ref": vdu["id"],
                         # TODO      "name": ""     Name of the VDU in the VIM
@@ -196,8 +211,32 @@ class NsrTopic(BaseTopic):
                             # "ip-address", "mac-address" # filled by LCM
                             # vim-id  # TODO it would be nice having a vim port id
                         }
+                        if vnfd_mgmt_cp and iface.get("external-connection-point-ref") == vnfd_mgmt_cp:
+                            vdu_iface["mgmt-vnf"] = True
                         if iface.get("mgmt-interface"):
-                            vdu_iface["mgmt-interface"] = True
+                            vdu_iface["mgmt-interface"] = True  # TODO change to mgmt-vdu
+
+                        # look for network where this interface is connected
+                        if iface.get("external-connection-point-ref"):
+                            for nsd_vld in get_iterable(nsd.get("vld")):
+                                for nsd_vld_cp in get_iterable(nsd_vld.get("vnfd-connection-point-ref")):
+                                    if nsd_vld_cp.get("vnfd-connection-point-ref") == \
+                                            iface["external-connection-point-ref"] and \
+                                            nsd_vld_cp.get("member-vnf-index-ref") == member_vnf["member-vnf-index"]:
+                                        vdu_iface["ns-vld-id"] = nsd_vld["id"]
+                                        break
+                                else:
+                                    continue
+                                break
+                        elif iface.get("internal-connection-point-ref"):
+                            for vnfd_ivld in get_iterable(vnfd.get("internal-vld")):
+                                for vnfd_ivld_icp in get_iterable(vnfd_ivld.get("internal-connection-point")):
+                                    if vnfd_ivld_icp.get("id-ref") == iface["internal-connection-point-ref"]:
+                                        vdu_iface["vnf-vld-id"] = vnfd_ivld["id"]
+                                        break
+                                else:
+                                    continue
+                                break
 
                         vdur["interfaces"].append(vdu_iface)
                     count = vdu.get("count", 1)
@@ -402,26 +441,38 @@ class NsLcmOpTopic(BaseTopic):
                     raise EngineException("Invalid parameter vld:name='{}' is not present at nsd:vld".format(
                         in_vld["name"]))
 
-    def _look_for_pdu(self, session, rollback, vnfr, vim_account):
+    def _look_for_pdu(self, session, rollback, vnfr, vim_account, vnfr_update, vnfr_update_rollback):
         """
-        Look for a free PDU in the catalog matching vdur type and interfaces. Fills vdur with ip_address information
-        :param vdur: vnfr:vdur descriptor. It is modified with pdu interface info if pdu is found
-        :param member_vnf_index: used just for logging. Target vnfd of nsd
-        :param vim_account:
-        :return: vder_update: dictionary to update vnfr:vdur with pdu info. In addition it modified choosen pdu to set
-        at status IN_USE
+        Look for a free PDU in the catalog matching vdur type and interfaces. Fills vnfr.vdur with the interface
+        (ip_address, ...) information.
+        Modifies PDU _admin.usageState to 'IN_USE'
+        
+        :param session: client session information
+        :param rollback: list with the database modifications to rollback if needed
+        :param vnfr: vnfr to be updated. It is modified with pdu interface info if pdu is found
+        :param vim_account: vim_account where this vnfr should be deployed
+        :param vnfr_update: dictionary filled by this method with changes to be done at database vnfr
+        :param vnfr_update_rollback: dictionary filled by this method with original content of vnfr in case a rollback
+                                     of the changed vnfr is needed
+
+        :return: List of PDU interfaces that are connected to an existing VIM network. Each item contains:
+                 "vim-network-name": used at VIM
+                  "name": interface name
+                  "vnf-vld-id": internal VNFD vld where this interface is connected, or
+                  "ns-vld-id": NSD vld where this interface is connected.
+                  NOTE: One, and only one between 'vnf-vld-id' and 'ns-vld-id' contains a value. The other will be None
         """
-        vnfr_update = {}
-        rollback_vnfr = {}
+
+        ifaces_forcing_vim_network = []
         for vdur_index, vdur in enumerate(get_iterable(vnfr.get("vdur"))):
             if not vdur.get("pdu-type"):
                 continue
             pdu_type = vdur.get("pdu-type")
             pdu_filter = self._get_project_filter(session, write=True, show_all=True)
-            pdu_filter["vim.vim_accounts"] = vim_account
+            pdu_filter["vim_accounts"] = vim_account
             pdu_filter["type"] = pdu_type
             pdu_filter["_admin.operationalState"] = "ENABLED"
-            pdu_filter["_admin.usageState"] = "NOT_IN_USE",
+            pdu_filter["_admin.usageState"] = "NOT_IN_USE"
             # TODO feature 1417: "shared": True,
 
             available_pdus = self.db.get_list("pdus", pdu_filter)
@@ -440,8 +491,8 @@ class NsLcmOpTopic(BaseTopic):
                     break
             else:
                 raise EngineException(
-                    "No PDU of type={} found for member_vnf_index={} at vim_account={} matching interface "
-                    "names".format(vdur["vdu-id-ref"], vnfr["member-vnf-index-ref"], pdu_type))
+                    "No PDU of type={} at vim_account={} found for member_vnf_index={}, vdu={} matching interface "
+                    "names".format(pdu_type, vim_account, vnfr["member-vnf-index-ref"], vdur["vdu-id-ref"]))
 
             # step 2. Update pdu
             rollback_pdu = {
@@ -460,21 +511,33 @@ class NsLcmOpTopic(BaseTopic):
 
             # step 3. Fill vnfr info by filling vdur
             vdu_text = "vdur.{}".format(vdur_index)
-            rollback_vnfr[vdu_text + ".pdu-id"] = None
+            vnfr_update_rollback[vdu_text + ".pdu-id"] = None
             vnfr_update[vdu_text + ".pdu-id"] = pdu["_id"]
             for iface_index, vdur_interface in enumerate(vdur["interfaces"]):
                 for pdu_interface in pdu["interfaces"]:
                     if pdu_interface["name"] == vdur_interface["name"]:
                         iface_text = vdu_text + ".interfaces.{}".format(iface_index)
                         for k, v in pdu_interface.items():
-                            vnfr_update[iface_text + ".{}".format(k)] = v
-                            rollback_vnfr[iface_text + ".{}".format(k)] = vdur_interface.get(v)
+                            if k in ("ip-address", "mac-address"):  # TODO: switch-xxxxx must be inserted
+                                vnfr_update[iface_text + ".{}".format(k)] = v
+                                vnfr_update_rollback[iface_text + ".{}".format(k)] = vdur_interface.get(v)
+                        if pdu_interface.get("ip-address"):
+                            if vdur_interface.get("mgmt-interface"):
+                                vnfr_update_rollback[vdu_text + ".ip-address"] = vdur.get("ip-address")
+                                vnfr_update[vdu_text + ".ip-address"] = pdu_interface["ip-address"]
+                            if vdur_interface.get("mgmt-vnf"):
+                                vnfr_update_rollback["ip-address"] = vnfr.get("ip-address")
+                                vnfr_update["ip-address"] = pdu_interface["ip-address"]
+                        if pdu_interface.get("vim-network-name"):  # or pdu_interface.get("vim-network-id"):
+                            ifaces_forcing_vim_network.append({
+                                # "vim-network-id": pdu_interface.get("vim-network-id"),
+                                "vim-network-name": pdu_interface.get("vim-network-name"),
+                                "name": vdur_interface.get("vnf-vld-id") or vdur_interface.get("ns-vld-id"),
+                                "vnf-vld-id": vdur_interface.get("vnf-vld-id"),
+                                "ns-vld-id": vdur_interface.get("ns-vld-id")})
                         break
 
-        if vnfr_update:
-            self.db.set_one("vnfrs", {"_id": vnfr["_id"]}, vnfr_update)
-            rollback.append({"topic": "vnfrs", "_id": vnfr["_id"], "operation": "set", "content": rollback_vnfr})
-        return
+        return ifaces_forcing_vim_network
 
     def _update_vnfrs(self, session, rollback, nsr, indata):
         vnfrs = None
@@ -500,10 +563,42 @@ class NsLcmOpTopic(BaseTopic):
             vnfr_update_rollback["vim-account-id"] = vnfr.get("vim-account-id")
 
             # get pdu
-            self._look_for_pdu(session, rollback, vnfr, vim_account)
-            # TODO change instantiation parameters to set network
+            ifaces_forcing_vim_network = self._look_for_pdu(session, rollback, vnfr, vim_account, vnfr_update,
+                                                            vnfr_update_rollback)
+
+            # updata database vnfr
+            self.db.set_one("vnfrs", {"_id": vnfr["_id"]}, vnfr_update)
+            rollback.append({"topic": "vnfrs", "_id": vnfr["_id"], "operation": "set", "content": vnfr_update_rollback})
 
-    def _create_nslcmop(self, session, nsInstanceId, operation, params):
+            # Update indada in case pdu forces to use a concrete vim-network-name
+            # TODO check if user has already insert a vim-network-name and raises an error
+            if not ifaces_forcing_vim_network:
+                continue
+            for iface_info in ifaces_forcing_vim_network:
+                if iface_info.get("ns-vld-id"):
+                    if "vld" not in indata:
+                        indata["vld"] = []
+                    indata["vld"].append({key: iface_info[key] for key in
+                                          ("name", "vim-network-name", "vim-network-id") if iface_info.get(key)})
+
+                elif iface_info.get("vnf-vld-id"):
+                    if "vnf" not in indata:
+                        indata["vnf"] = []
+                    indata["vnf"].append({
+                        "member-vnf-index": member_vnf_index,
+                        "internal-vld": [{key: iface_info[key] for key in
+                                          ("name", "vim-network-name", "vim-network-id") if iface_info.get(key)}]
+                    })
+
+    @staticmethod
+    def _create_nslcmop(nsr_id, operation, params):
+        """
+        Creates a ns-lcm-opp content to be stored at database.
+        :param nsr_id: internal id of the instance
+        :param operation: instantiate, terminate, scale, action, ...
+        :param params: user parameters for the operation
+        :return: dictionary following SOL005 format
+        """
         now = time()
         _id = str(uuid4())
         nslcmop = {
@@ -511,7 +606,7 @@ class NsLcmOpTopic(BaseTopic):
             "_id": _id,
             "operationState": "PROCESSING",  # COMPLETED,PARTIALLY_COMPLETED,FAILED_TEMP,FAILED,ROLLING_BACK,ROLLED_BACK
             "statusEnteredTime": now,
-            "nsInstanceId": nsInstanceId,
+            "nsInstanceId": nsr_id,
             "lcmOperationType": operation,
             "startTime": now,
             "isAutomaticInvocation": False,
@@ -519,7 +614,7 @@ class NsLcmOpTopic(BaseTopic):
             "isCancelPending": False,
             "links": {
                 "self": "/osm/nslcm/v1/ns_lcm_op_occs/" + _id,
-                "nsInstance": "/osm/nslcm/v1/ns_instances/" + nsInstanceId,
+                "nsInstance": "/osm/nslcm/v1/ns_instances/" + nsr_id,
             }
         }
         return nslcmop
@@ -564,9 +659,11 @@ class NsLcmOpTopic(BaseTopic):
                     raise EngineException("ns_instance '{}' cannot be '{}' because it is already instantiated".format(
                         nsInstanceId, operation), HTTPStatus.CONFLICT)
             self._check_ns_operation(session, nsr, operation, indata)
+
             if operation == "instantiate":
                 self._update_vnfrs(session, rollback, nsr, indata)
-            nslcmop_desc = self._create_nslcmop(session, nsInstanceId, operation, indata)
+
+            nslcmop_desc = self._create_nslcmop(nsInstanceId, operation, indata)
             self.format_on_new(nslcmop_desc, session["project_id"], make_public=make_public)
             _id = self.db.create("nslcmops", nslcmop_desc)
             rollback.append({"topic": "nslcmops", "_id": _id})
index 215211d..1dbc9ca 100644 (file)
@@ -50,7 +50,7 @@ port: 27017
 name: "osm"
 # user: "user"
 # password: "password"
-# materpassword: "mpasswd"
+# commonkey: "commonkey"
 
 loglevel:  "DEBUG"
 #logfile: /var/log/osm/nbi-database.log
index 1354f2c..b7a6990 100644 (file)
@@ -14,6 +14,7 @@ import sys
 from authconn import AuthException
 from auth import Authenticator
 from engine import Engine, EngineException
+from validation import ValidationError
 from osm_common.dbbase import DbException
 from osm_common.fsbase import FsException
 from osm_common.msgbase import MsgException
@@ -118,6 +119,10 @@ URL: /osm                                                       GET     POST
 
 query string:
     Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
+        simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
+        filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
+        op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
+        attrName := string
     For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
     item of the array, that is, pass if any item of the array pass the filter.
     It allows both ne and neq for not equal
@@ -214,7 +219,7 @@ class Server(object):
                                                "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
                                                },
                     "ns_descriptors": {"METHODS": ("GET", "POST"),
-                                       "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH",
+                                       "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
                                                 "nsd_content": {"METHODS": ("GET", "PUT")},
                                                 "nsd": {"METHODS": "GET"},  # descriptor inside package
                                                 "artifacts": {"*": {"METHODS": "GET"}}
@@ -661,7 +666,7 @@ class Server(object):
             if not main_topic or not version or not topic:
                 raise NbiException("URL must contain at least 'main_topic/version/topic'",
                                    HTTPStatus.METHOD_NOT_ALLOWED)
-            if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "nst", "nsilcm"):
+            if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm"):
                 raise NbiException("URL main_topic '{}' not supported".format(main_topic),
                                    HTTPStatus.METHOD_NOT_ALLOWED)
             if version != 'v1':
@@ -825,7 +830,8 @@ class Server(object):
                 raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
             return self._format_out(outdata, session, _format)
         except Exception as e:
-            if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException)):
+            if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException,
+                              ValidationError)):
                 http_code_value = cherrypy.response.status = e.http_code.value
                 http_code_name = e.http_code.name
                 cherrypy.log("Exception {}".format(e))
index c670106..dd95417 100755 (executable)
@@ -66,7 +66,7 @@ then
     done
 fi
 
-for item in vim_accounts sdns nsrs vnfrs nslcmops nsds vnfds projects nsts nsis nsilcmops # vims
+for item in vim_accounts sdns nsrs vnfrs nslcmops nsds vnfds projects pdus nsts nsis nsilcmops # vims
 do
     curl --insecure ${OSMNBI_URL}/test/db-clear/${item}
     echo " ${item}"
index 2fbc217..51e1494 100755 (executable)
@@ -116,10 +116,15 @@ class TestRest:
         # contains ID of tests obtained from Location response header. "" key contains last obtained id
         self.test_ids = {}
         self.old_test_description = ""
+        self.test_name = None
+        self.step = 0
 
     def set_header(self, header):
         self.s.headers.update(header)
 
+    def set_tet_name(self, test_name):
+        self.test_name = test_name
+
     def unset_header(self, key):
         if key in self.s.headers:
             del self.s.headers[key]
@@ -240,6 +245,7 @@ class TestRest:
                 _id = location[location.rfind("/") + 1:]
                 if _id:
                     self.test_ids[name] = str(_id)
+                    self.test_ids["last_id"] = str(_id)  # last id
                     self.test_ids[""] = str(_id)  # last id
             return r
         except TestException as e:
@@ -587,6 +593,7 @@ class TestDeploy:
         self.descriptor_url = "https://osm-download.etsi.org/ftp/osm-3.0-three/2nd-hackfest/packages/"
         self.vnfd_filenames = ("cirros_vnf.tar.gz",)
         self.nsd_filename = "cirros_2vnf_ns.tar.gz"
+        self.descriptor_edit = None
         self.uses_configuration = False
         self.uss = {}
         self.passwds = {}
@@ -595,12 +602,13 @@ class TestDeploy:
         self.timeout = 120
         self.passed_tests = 0
         self.total_tests = 0
+        self.qforce = ""
 
     def create_descriptors(self, engine):
         temp_dir = os.path.dirname(os.path.abspath(__file__)) + "/temp/"
         if not os.path.exists(temp_dir):
             os.makedirs(temp_dir)
-        for vnfd_filename in self.vnfd_filenames:
+        for vnfd_index, vnfd_filename in enumerate(self.vnfd_filenames):
             if "/" in vnfd_filename:
                 vnfd_filename_path = vnfd_filename
                 if not os.path.exists(vnfd_filename_path):
@@ -622,10 +630,11 @@ class TestDeploy:
                 # vnfd CREATE AND UPLOAD in one step:
                 test_name = "DEPLOY{}".format(self.step)
                 engine.test(test_name, "Onboard VNFD in one step", "POST",
-                            "/vnfpkgm/v1/vnf_packages_content", headers, "@b" + vnfd_filename_path, 201,
-                            {"Location": "/vnfpkgm/v1/vnf_packages_content/", "Content-Type": "application/yaml"}, yaml)
+                            "/vnfpkgm/v1/vnf_packages_content" + self.qforce, headers, "@b" + vnfd_filename_path, 201,
+                            {"Location": "/vnfpkgm/v1/vnf_packages_content/", "Content-Type": "application/yaml"},
+                            "yaml")
                 self.vnfds_test.append(test_name)
-                self.vnfds_id.append(engine.test_ids[test_name])
+                self.vnfds_id.append(engine.test_ids["last_id"])
                 self.step += 1
             else:
                 # vnfd CREATE AND UPLOAD ZIP
@@ -634,15 +643,23 @@ class TestDeploy:
                             headers_json, None, 201,
                             {"Location": "/vnfpkgm/v1/vnf_packages/", "Content-Type": "application/json"}, "json")
                 self.vnfds_test.append(test_name)
-                self.vnfds_id.append(engine.test_ids[test_name])
+                self.vnfds_id.append(engine.test_ids["last_id"])
                 self.step += 1
                 # location = r.headers["Location"]
                 # vnfd_id = location[location.rfind("/")+1:]
                 engine.test("DEPLOY{}".format(self.step), "Onboard VNFD step 2 as ZIP", "PUT",
-                            "/vnfpkgm/v1/vnf_packages/<>/package_content",
+                            "/vnfpkgm/v1/vnf_packages/<>/package_content" + self.qforce,
                             headers, "@b" + vnfd_filename_path, 204, None, 0)
                 self.step += 2
 
+            if self.descriptor_edit:
+                if "vnfd{}".format(vnfd_index) in self.descriptor_edit:
+                    # Modify VNFD
+                    engine.test("DEPLOY{}".format(self.step), "Edit VNFD ", "PATCH",
+                                "/vnfpkgm/v1/vnf_packages/{}".format(self.vnfds_id[-1]),
+                                headers_yaml, self.descriptor_edit["vnfd{}".format(vnfd_index)], 204, None, None)
+                    self.step += 1
+
         if "/" in self.nsd_filename:
             nsd_filename_path = self.nsd_filename
             if not os.path.exists(nsd_filename_path):
@@ -665,32 +682,40 @@ class TestDeploy:
         if self.step % 2 == 0:
             # nsd CREATE AND UPLOAD in one step:
             engine.test("DEPLOY{}".format(self.step), "Onboard NSD in one step", "POST",
-                        "/nsd/v1/ns_descriptors_content", headers, "@b" + nsd_filename_path, 201,
+                        "/nsd/v1/ns_descriptors_content" + self.qforce, headers, "@b" + nsd_filename_path, 201,
                         {"Location": "/nsd/v1/ns_descriptors_content/", "Content-Type": "application/yaml"}, yaml)
             self.step += 1
+            self.nsd_id = engine.test_ids["last_id"]
         else:
             # nsd CREATE AND UPLOAD ZIP
             engine.test("DEPLOY{}".format(self.step), "Onboard NSD step 1", "POST", "/nsd/v1/ns_descriptors",
                         headers_json, None, 201,
                         {"Location": "/nsd/v1/ns_descriptors/", "Content-Type": "application/json"}, "json")
             self.step += 1
+            self.nsd_id = engine.test_ids["last_id"]
             # location = r.headers["Location"]
             # vnfd_id = location[location.rfind("/")+1:]
             engine.test("DEPLOY{}".format(self.step), "Onboard NSD step 2 as ZIP", "PUT",
-                        "/nsd/v1/ns_descriptors/<>/nsd_content",
+                        "/nsd/v1/ns_descriptors/<>/nsd_content" + self.qforce,
                         headers, "@b" + nsd_filename_path, 204, None, 0)
             self.step += 2
-        self.nsd_id = engine.test_ids[self.nsd_test]
+
+        if self.descriptor_edit and "nsd" in self.descriptor_edit:
+            # Modify NSD
+            engine.test("DEPLOY{}".format(self.step), "Edit NSD ", "PATCH",
+                        "/nsd/v1/ns_descriptors/{}".format(self.nsd_id),
+                        headers_yaml, self.descriptor_edit["nsd"], 204, None, None)
+            self.step += 1
 
     def delete_descriptors(self, engine):
         # delete descriptors
         engine.test("DEPLOY{}".format(self.step), "Delete NSSD SOL005", "DELETE",
-                    "/nsd/v1/ns_descriptors/<{}>".format(self.nsd_test),
+                    "/nsd/v1/ns_descriptors/{}".format(self.nsd_id),
                     headers_yaml, None, 204, None, 0)
         self.step += 1
-        for vnfd_test in self.vnfds_test:
+        for vnfd_id in self.vnfds_id:
             engine.test("DEPLOY{}".format(self.step), "Delete VNFD SOL005", "DELETE",
-                        "/vnfpkgm/v1/vnf_packages/<{}>".format(vnfd_test), headers_yaml, None, 204, None, 0)
+                        "/vnfpkgm/v1/vnf_packages/{}".format(vnfd_id), headers_yaml, None, 204, None, 0)
             self.step += 1
 
     def instantiate(self, engine, ns_data):
@@ -700,8 +725,7 @@ class TestDeploy:
                         headers_yaml, ns_data_text, 201,
                         {"Location": "nslcm/v1/ns_instances/", "Content-Type": "application/yaml"}, "yaml")
         self.ns_test = "DEPLOY{}".format(self.step)
-        self.ns_id = engine.test_ids[self.ns_test]
-        engine.test_ids[self.ns_test]
+        self.ns_id = engine.test_ids["last_id"]
         self.step += 1
         r = engine.test("DEPLOY{}".format(self.step), "Instantiate NS step 2", "POST",
                         "/nslcm/v1/ns_instances/<{}>/instantiate".format(self.ns_test), headers_yaml, ns_data_text,
@@ -835,8 +859,8 @@ class TestDeploy:
             from pssh.utils import load_private_key
             from ssh2 import exceptions as ssh2Exception
         except ImportError as e:
-            logger.critical("package <pssh> or/and <urllib3> is not installed. Please add it with 'pip3 install "
-                            "parallel-ssh' and/or 'pip3 install urllib3': {}".format(e))
+            logger.critical("Package <pssh> or/and <urllib3> is not installed. Please add them with 'pip3 install "
+                            "parallel-ssh urllib3': {}".format(e))
         urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
         try:
             p_host = os.environ.get("PROXY_HOST")
@@ -892,7 +916,7 @@ class TestDeploy:
 
         if manual_check:
             input('NS has been deployed. Perform manual check and press enter to resume')
-        else:
+        elif test_osm:
             self.test_ns(engine, test_osm, self.cmds, self.uss, self.pss, self.keys, self.timeout)
         self.aditional_operations(engine, test_osm, manual_check)
         self.terminate(engine)
@@ -1189,7 +1213,7 @@ class TestDeployHackfest3Charmed(TestDeploy):
         if manual_check:
             input('NS service primitive has been executed. Check that file /home/ubuntu/OSMTESTNBI is present at '
                   'TODO_PUT_IP')
-        else:
+        elif test_osm:
             cmds = {'1': [''], '2': ['ls -lrt /home/ubuntu/OSMTESTNBI', ]}
             uss = {'1': "ubuntu", '2': "ubuntu"}
             pss = {'1': "osm4u", '2': "osm4u"}
@@ -1220,6 +1244,234 @@ class TestDeployHackfest3Charmed(TestDeploy):
         # # TODO check automatic
 
 
+class TestDeploySingleVdu(TestDeployHackfest3Charmed):
+    description = "Generate a single VDU base on editing Hackfest3Charmed descriptors and deploy"
+
+    def __init__(self):
+        super().__init__()
+        self.qforce = "?FORCE=True"
+        self.descriptor_edit = {
+            # Modify VNFD to remove one VDU
+            "vnfd0": {
+                "vdu": {
+                    "$[0]": {
+                        "interface": {"$[0]": {"external-connection-point-ref": "pdu-mgmt"}}
+                    },
+                    "$[1]": None
+                },
+                "vnf-configuration": None,
+                "connection-point": {
+                    "$[0]": {
+                        "id": "pdu-mgmt",
+                        "name": "pdu-mgmt",
+                        "short-name": "pdu-mgmt"
+                    },
+                    "$[1]": None
+                },
+                "mgmt-interface": {"cp": "pdu-mgmt"},
+                "description": "A vnf single vdu to be used as PDU",
+                "id": "vdu-as-pdu",
+                "internal-vld": {
+                    "$[0]": {
+                        "id": "pdu_internal",
+                        "name": "pdu_internal",
+                        "internal-connection-point": {"$[1]": None},
+                        "short-name": "pdu_internal",
+                        "type": "ELAN"
+                    }
+                }
+            },
+
+            # Modify NSD accordingly
+            "nsd": {
+                "constituent-vnfd": {
+                    "$[0]": {"vnfd-id-ref": "vdu-as-pdu"},
+                    "$[1]": None,
+                },
+                "description": "A nsd to deploy the vnf to act as as PDU",
+                "id": "nsd-as-pdu",
+                "name": "nsd-as-pdu",
+                "short-name": "nsd-as-pdu",
+                "vld": {
+                    "$[0]": {
+                        "id": "mgmt_pdu",
+                        "name": "mgmt_pdu",
+                        "short-name": "mgmt_pdu",
+                        "vnfd-connection-point-ref": {
+                            "$[0]": {
+                                "vnfd-connection-point-ref": "pdu-mgmt",
+                                "vnfd-id-ref": "vdu-as-pdu",
+                            },
+                            "$[1]": None
+                        },
+                        "type": "ELAN"
+                    },
+                    "$[1]": None,
+                }
+            }
+        }
+
+
+class TestDeployHnfd(TestDeployHackfest3Charmed):
+    description = "Generate a HNFD base on editing Hackfest3Charmed descriptors and deploy"
+
+    def __init__(self):
+        super().__init__()
+        self.pduDeploy = TestDeploySingleVdu()
+        self.pdu_interface_0 = {}
+        self.pdu_interface_1 = {}
+
+        self.pdu_id = None
+        # self.vnf_to_pdu = """
+        #     vdu:
+        #         "$[0]":
+        #             pdu-type: PDU-TYPE-1
+        #             interface:
+        #                 "$[0]":
+        #                     name: mgmt-iface
+        #                 "$[1]":
+        #                     name: pdu-iface-internal
+        #     id: hfn1
+        #     description: HFND, one PDU + One VDU
+        #     name: hfn1
+        #     short-name: hfn1
+        #
+        # """
+
+        self.pdu_descriptor = {
+            "name": "my-PDU",
+            "type": "PDU-TYPE-1",
+            "vim_accounts": "to-override",
+            "interfaces": [
+                {
+                    "name": "mgmt-iface",
+                    "mgmt": True,
+                    "type": "overlay",
+                    "ip-address": "to override",
+                    "mac-address": "mac_address",
+                    "vim-network-name": "mgmt",
+                },
+                {
+                    "name": "pdu-iface-internal",
+                    "mgmt": False,
+                    "type": "overlay",
+                    "ip-address": "to override",
+                    "mac-address": "mac_address",
+                    "vim-network-name": "pdu_internal",  # OSMNBITEST-PDU-pdu_internal
+                },
+            ]
+        }
+        self.vnfd_filenames = ("hackfest_3charmed_vnfd.tar.gz", "hackfest_3charmed_vnfd.tar.gz")
+
+        self.descriptor_edit = {
+            "vnfd0": {
+                "id": "hfnd1",
+                "name": "hfn1",
+                "short-name": "hfn1",
+                "vdu": {
+                    "$[0]": {
+                        "pdu-type": "PDU-TYPE-1",
+                        "interface": {
+                            "$[0]": {"name": "mgmt-iface"},
+                            "$[1]": {"name": "pdu-iface-internal"},
+                        }
+                    }
+                }
+            },
+            "nsd": {
+                "constituent-vnfd": {
+                    "$[1]": {"vnfd-id-ref": "hfnd1"}
+                }
+            }
+        }
+
+    def create_descriptors(self, engine):
+        super().create_descriptors(engine)
+
+        # Create PDU
+        self.pdu_descriptor["interfaces"][0].update(self.pdu_interface_0)
+        self.pdu_descriptor["interfaces"][1].update(self.pdu_interface_1)
+        self.pdu_descriptor["vim_accounts"] = [self.vim_id]
+        # TODO get vim-network-name from vnfr.vld.name
+        self.pdu_descriptor["interfaces"][1]["vim-network-name"] = "{}-{}-{}".format(
+            os.environ.get("OSMNBITEST_NS_NAME", "OSMNBITEST"),
+            "PDU", self.pdu_descriptor["interfaces"][1]["vim-network-name"])
+        test_name = "DEPLOY{}".format(self.step)
+        engine.test(test_name, "Onboard PDU descriptor", "POST", "/pdu/v1/pdu_descriptors",
+                    {"Location": "/pdu/v1/pdu_descriptors/", "Content-Type": "application/yaml"}, self.pdu_descriptor,
+                    201, r_header_yaml, "yaml")
+        self.pdu_id = engine.test_ids["last_id"]
+        self.step += 1
+
+    def run(self, engine, test_osm, manual_check, test_params=None):
+        engine.get_autorization()
+        nsname = os.environ.get("OSMNBITEST_NS_NAME", "OSMNBITEST")
+
+        # create real VIM if not exist
+        self.vim_id = engine.get_create_vim(test_osm)
+        # instanciate PDU
+        self.pduDeploy.create_descriptors(engine)
+        self.pduDeploy.instantiate(engine, {"nsDescription": "to be used as PDU", "nsName": nsname + "-PDU",
+                                            "nsdId": self.pduDeploy.nsd_id, "vimAccountId": self.vim_id})
+        if manual_check:
+            input('VNF to be used as PDU has been deployed. Perform manual check and press enter to resume')
+        elif test_osm:
+            self.pduDeploy.test_ns(engine, test_osm, self.pduDeploy.cmds, self.pduDeploy.uss, self.pduDeploy.pss,
+                                   self.pduDeploy.keys, self.pduDeploy.timeout)
+
+        if test_osm:
+            r = engine.test("DEPLOY{}".format(self.step), "GET IP_ADDRESS OF VNFR", "GET",
+                            "/nslcm/v1/vnfrs?nsr-id-ref={}".format(self.pduDeploy.ns_id), headers_json, None,
+                            200, r_header_json, "json")
+            self.step += 1
+            vnfr_data = r.json()
+            # print(vnfr_data)
+
+            self.pdu_interface_0["ip-address"] = vnfr_data[0]["vdur"][0]["interfaces"][0].get("ip-address")
+            self.pdu_interface_1["ip-address"] = vnfr_data[0]["vdur"][0]["interfaces"][1].get("ip-address")
+            self.pdu_interface_0["mac-address"] = vnfr_data[0]["vdur"][0]["interfaces"][0].get("mac-address")
+            self.pdu_interface_1["mac-address"] = vnfr_data[0]["vdur"][0]["interfaces"][1].get("mac-address")
+            if not self.pdu_interface_0["ip-address"]:
+                raise TestException("Vnfr has not managment ip address")
+        else:
+            self.pdu_interface_0["ip-address"] = "192.168.10.10"
+            self.pdu_interface_1["ip-address"] = "192.168.11.10"
+            self.pdu_interface_0["mac-address"] = "52:33:44:55:66:13"
+            self.pdu_interface_1["mac-address"] = "52:33:44:55:66:14"
+
+        self.create_descriptors(engine)
+
+        ns_data = {"nsDescription": "default description", "nsName": nsname, "nsdId": self.nsd_id,
+                   "vimAccountId": self.vim_id}
+        if test_params and test_params.get("ns-config"):
+            if isinstance(test_params["ns-config"], str):
+                ns_data.update(yaml.load(test_params["ns-config"]))
+            else:
+                ns_data.update(test_params["ns-config"])
+
+        self.instantiate(engine, ns_data)
+        if manual_check:
+            input('NS has been deployed. Perform manual check and press enter to resume')
+        elif test_osm:
+            self.test_ns(engine, test_osm, self.cmds, self.uss, self.pss, self.keys, self.timeout)
+        self.aditional_operations(engine, test_osm, manual_check)
+        self.terminate(engine)
+        self.pduDeploy.terminate(engine)
+        self.delete_descriptors(engine)
+        self.pduDeploy.delete_descriptors(engine)
+
+        self.step += 1
+
+        self.print_results()
+
+    def delete_descriptors(self, engine):
+        super().delete_descriptors(engine)
+        # delete pdu
+        engine.test("DEPLOY{}".format(self.step), "Delete PDU SOL005", "DELETE",
+                    "/pdu/v1/pdu_descriptors/{}".format(self.pdu_id),
+                    headers_yaml, None, 204, None, 0)
+
+
 class TestDescriptors:
     description = "Test VNFD, NSD, PDU descriptors CRUD and dependencies"
 
@@ -1256,7 +1508,7 @@ class TestDescriptors:
         engine.test(test_name, "Onboard VNFD in one step", "POST",
                     "/vnfpkgm/v1/vnf_packages_content", headers_zip_yaml, "@b" + vnfd_filename_path, 201,
                     {"Location": "/vnfpkgm/v1/vnf_packages_content/", "Content-Type": "application/yaml"}, "yaml")
-        self.vnfd_id = engine.test_ids[test_name]
+        self.vnfd_id = engine.test_ids["last_id"]
         self.step += 1
 
         # get vnfd descriptor
@@ -1290,7 +1542,7 @@ class TestDescriptors:
         engine.test(test_name, "Onboard NSD in one step", "POST",
                     "/nsd/v1/ns_descriptors_content", headers_zip_yaml, "@b" + nsd_filename_path, 201,
                     {"Location": "/nsd/v1/ns_descriptors_content/", "Content-Type": "application/yaml"}, "yaml")
-        self.nsd_id = engine.test_ids[test_name]
+        self.nsd_id = engine.test_ids["last_id"]
         self.step += 1
 
         # get nsd descriptor
@@ -1395,6 +1647,8 @@ if __name__ == "__main__":
             "TestDescriptors": TestDescriptors,
             "TestDeployHackfest1": TestDeployHackfest1,
             # "Deploy-MultiVIM": TestDeployMultiVIM,
+            "DeploySingleVdu": TestDeploySingleVdu,
+            "DeployHnfd": TestDeployHnfd,
             "Upload-Slice-Template": TestNstTemplates,
         }
         test_to_do = []
index 62d9d22..b8953b5 100644 (file)
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 
 from jsonschema import validate as js_v, exceptions as js_e
+from http import HTTPStatus
 
 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 __version__ = "0.1"
@@ -427,18 +428,18 @@ pdu_interface = {
         "name": nameshort_schema,
         "mgmt": bool_schema,
         "type": {"enum": ["overlay", 'underlay']},
-        "ip_address": ip_schema,
+        "ip-address": ip_schema,
         # TODO, add user, password, ssh-key
-        "mac_address": mac_schema,
-        "vim_network_name": nameshort_schema,  # interface is connected to one vim network, or switch port
-        "vim_network_id": nameshort_schema,
-        # provide this in case SDN assist must deal with this interface
-        "switch_dpid": dpid_Schema,
-        "switch_port": nameshort_schema,
-        "switch_mac": nameshort_schema,
-        "switch_vlan": vlan_schema,
+        "mac-address": mac_schema,
+        "vim-network-name": nameshort_schema,  # interface is connected to one vim network, or switch port
+        # TODO "vim-network-id": nameshort_schema,
+        # provide this in case SDN assist must deal with this interface
+        # "switch-dpid": dpid_Schema,
+        # "switch-port": nameshort_schema,
+        # "switch-mac": nameshort_schema,
+        # "switch-vlan": vlan_schema,
     },
-    "required": ["name", "mgmt", "ip_address"],
+    "required": ["name", "mgmt", "ip-address"],
     "additionalProperties": False
 }
 pdu_new_schema = {
@@ -454,7 +455,7 @@ pdu_new_schema = {
         "vim_accounts": nameshort_list_schema,
         "interfaces": {
             "type": "array",
-            "items": {"type": pdu_interface},
+            "items": pdu_interface,
             "minItems": 1
         }
     },
@@ -477,7 +478,7 @@ pdu_edit_schema = {
             array_edition_schema,
             {
                 "type": "array",
-                "items": {"type": pdu_interface},
+                "items": pdu_interface,
                 "minItems": 1
             }
         ]}
@@ -594,7 +595,9 @@ nsi_terminate = {
 
 
 class ValidationError(Exception):
-    pass
+    def __init__(self, message, http_code=HTTPStatus.UNPROCESSABLE_ENTITY):
+        self.http_code = http_code
+        Exception.__init__(self, message)
 
 
 def validate_input(indata, schema_to_use):
@@ -614,3 +617,5 @@ def validate_input(indata, schema_to_use):
         else:
             error_pos = ""
         raise ValidationError("Format error {} '{}' ".format(error_pos, e.message))
+    except js_e.SchemaError:
+        raise ValidationError("Bad json schema {}".format(schema_to_use), http_code=HTTPStatus.INTERNAL_SERVER_ERROR)
index 8a293d5..0f3c616 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -27,6 +27,7 @@ setup(
 
     packages=[_name],
     include_package_data=True,
+    # exclude_package_data={'': ['osm_nbi/local', 'temp']},
     data_files=[('/etc/osm/', ['osm_nbi/nbi.cfg']),
                 ('/etc/systemd/system/', ['osm_nbi/osm-nbi.service']),
                 ],