Adding slice templates to NBI 26/6726/5
authorFelipe Vicens <felipe.vicens@atos.net>
Tue, 16 Oct 2018 14:00:20 +0000 (16:00 +0200)
committerFelipe Vicens <felipe.vicens@atos.net>
Tue, 13 Nov 2018 14:36:30 +0000 (15:36 +0100)
Change-Id: I3a99a71a1252b8796b5988fcade9dca849476ce4
Signed-off-by: Felipe Vicens <felipe.vicens@atos.net>
osm_nbi/descriptor_topics.py
osm_nbi/engine.py
osm_nbi/html_out.py
osm_nbi/instance_topics.py
osm_nbi/nbi.py
osm_nbi/tests/cirros_slice/cirros_slice.yaml [new file with mode: 0644]
osm_nbi/tests/clear-all.sh
osm_nbi/tests/test.py

index 503f82a..9e367c9 100644 (file)
@@ -634,6 +634,83 @@ 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:nst'):
+            clean_indata = clean_indata['nst:nst']
+        elif clean_indata.get('nst'):
+            clean_indata = clean_indata['nst']
+        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]
+        return clean_indata
+
+    def _validate_input_new(self, indata, force=False):
+        # transform netslice-subnet:nsd-ref to string
+        if indata.get("netslice-subnet"):
+            for nsd_ref in indata["netslice-subnet"]:
+                if "nsd-ref" in nsd_ref:
+                    nsd_ref["nsd-ref"] = str(nsd_ref["nsd-ref"])
+
+        # TODO validate with pyangbind, serialize
+        return indata
+
+    def _validate_input_edit(self, indata, force=False):
+        # TODO validate with pyangbind, serialize
+        return indata
+
+    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: nsd internal id
+        :param force: Avoid this checking
+        :return: None or raises EngineException with the conflict
+        """
+        # TODO: Check this method
+        if force:
+            return
+        _filter = self._get_project_filter(session, write=False, show_all=False)
+        _filter["nst"] = _id
+        if self.db.get_list("nsis", _filter):
+            raise EngineException("There is some NSIS that depends on this NST", http_code=HTTPStatus.CONFLICT)
+
+
 class PduTopic(BaseTopic):
     topic = "pdus"
     topic_msg = "pdu"
index 3cdacd9..3de3d15 100644 (file)
@@ -8,8 +8,8 @@ from osm_common.msgbase import MsgException
 from http import HTTPStatus
 from base_topic import EngineException, versiontuple
 from admin_topics import UserTopic, ProjectTopic, VimAccountTopic, SdnTopic
-from descriptor_topics import VnfdTopic, NsdTopic, PduTopic
-from instance_topics import NsrTopic, VnfrTopic, NsLcmOpTopic
+from descriptor_topics import VnfdTopic, NsdTopic, PduTopic, NstTopic
+from instance_topics import NsrTopic, VnfrTopic, NsLcmOpTopic, NsiTopic
 from base64 import b64encode
 from os import urandom
 
@@ -21,6 +21,7 @@ class Engine(object):
     map_from_topic_to_class = {
         "vnfds": VnfdTopic,
         "nsds": NsdTopic,
+        "nsts": NstTopic,
         "pdus": PduTopic,
         "nsrs": NsrTopic,
         "vnfrs": VnfrTopic,
@@ -29,6 +30,7 @@ class Engine(object):
         "sdns": SdnTopic,
         "users": UserTopic,
         "projects": ProjectTopic,
+        "nsis": NsiTopic,
         # [NEW_TOPIC]: add an entry here
     }
 
index 2fe4b8f..e9237e9 100644 (file)
@@ -24,6 +24,7 @@ html_start = """
       <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>
+      <a href="/osm/nst/v1/netslice_templates">NSTDs </a>
       <a href="/osm/admin/v1/users">USERs </a>
       <a href="/osm/admin/v1/projects">PROJECTs </a>
       <a href="/osm/admin/v1/tokens">TOKENs </a>
@@ -125,6 +126,8 @@ def format(data, request, response, session):
             body += html_upload_body.format(request.path_info, "VNFD")
         elif request.path_info == "/nsd/v1/ns_descriptors":
             body += html_upload_body.format(request.path_info + "_content", "NSD")
+        elif request.path_info == "/nst/v1/nst_templates":
+            body += html_upload_body.format(request.path_info + "_content", "NSTD")
         for k in data:
             if isinstance(k, dict):
                 data_id = k.pop("_id", None)
index 29a391c..4228bc5 100644 (file)
@@ -592,3 +592,152 @@ class NsLcmOpTopic(BaseTopic):
 
     def edit(self, session, _id, indata=None, kwargs=None, force=False, content=None):
         raise EngineException("Method edit called directly", HTTPStatus.INTERNAL_SERVER_ERROR)
+
+
+class NsiTopic(BaseTopic):
+    topic = "nsis"
+    topic_msg = "nsi"
+
+    def __init__(self, db, fs, msg):
+        BaseTopic.__init__(self, db, fs, msg)
+
+    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("nstdId"):
+            return
+        nstd_id = descriptor["nstdId"]
+        if not self.get_item_list(session, "nsts", {"id": nstd_id}):
+            raise EngineException("Descriptor error at nstdId='{}' references a non exist nstd".format(nstd_id),
+                                  http_code=HTTPStatus.CONFLICT)
+
+    @staticmethod
+    def format_on_new(content, project_id=None, make_public=False):
+        BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
+        content["_admin"]["nstState"] = "NOT_INSTANTIATED"
+
+    def check_conflict_on_del(self, session, _id, force=False):
+        if force:
+            return
+        nsi = self.db.get_one("nsis", {"_id": _id})
+        if nsi["_admin"].get("nsiState") == "INSTANTIATED":
+            raise EngineException("nsi '{}' cannot be deleted because it is in 'INSTANTIATED' state. "
+                                  "Launch 'terminate' operation first; or force deletion".format(_id),
+                                  http_code=HTTPStatus.CONFLICT)
+
+    def delete(self, session, _id, force=False, dry_run=False):
+        """
+        Delete item by its internal _id
+        :param session: contains the used login username, working project, and admin rights
+        :param _id: server internal id
+        :param force: indicates if deletion must be forced in case of conflict
+        :param dry_run: make checking but do not delete
+        :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
+        """
+        # TODO add admin to filter, validate rights
+        BaseTopic.delete(self, session, _id, force, dry_run=True)
+        if dry_run:
+            return
+        v = self.db.del_one("nsis", {"_id": _id})
+        self.db.del_list("nstlcmops", {"nstInstanceId": _id})
+        self.db.del_list("vnfrs", {"nsr-id-ref": _id})
+        # set all used pdus as free
+        self.db.set_list("pdus", {"_admin.usage.nsr_id": _id}, {"_admin.usageSate": "NOT_IN_USE", "_admin.usage": None})
+        self._send_msg("deleted", {"_id": _id})
+        return v
+
+    def new(self, rollback, session, indata=None, kwargs=None, headers=None, force=False, make_public=False):
+        """
+        Creates a new nsr into database. It also creates needed vnfrs
+        :param rollback: list to append the created items at database in case a rollback must be done
+        :param session: contains the used login username and working project
+        :param indata: params to be used for the nsir
+        :param kwargs: used to override the indata descriptor
+        :param headers: http request headers
+        :param force: If True avoid some dependence checks
+        :param make_public: Make the created item public to all projects
+        :return: the _id of nsi descriptor created at database
+        """
+
+        try:
+            slice_request = self._remove_envelop(indata)
+            # Override descriptor with query string kwargs
+            self._update_input_with_kwargs(slice_request, kwargs)
+            self._validate_input_new(slice_request, force)
+
+            step = ""
+            # look for nstd
+            step = "getting nstd id='{}' from database".format(slice_request.get("nstdId"))
+            _filter = {"_id": slice_request["nstdId"]}
+            _filter.update(BaseTopic._get_project_filter(session, write=False, show_all=True))
+            nstd = self.db.get_one("nsts", _filter)
+            nsi_id = str(uuid4())
+            # now = time()
+            step = "filling nsi_descriptor with input data"
+            nsi_descriptor = {
+                "id": nsi_id,
+                "nst-ref": nstd["id"],
+                "instantiation-parameters": {
+                    "netslice-subnet": []
+                },
+                "network-slice-template": nstd,
+                # "nsr-ref-list": [],    #TODO: not used for now...
+                # "vlr-ref-list": [],    #TODO: not used for now...
+                "_id": nsi_id,
+                
+                # TODO CHECK: what about the following params?
+                # "admin-status": "ENABLED",
+                # "description": slice_request.get("nsDescription", ""),
+                # "operational-status": "init",    # typedef ns-operational-
+                # "config-status": "init",         # typedef config-states
+                # "detailed-status": "scheduled",
+                # "orchestration-progress": {},
+                # # {"networks": {"active": 0, "total": 0}, "vms": {"active": 0, "total": 0}},
+                # "create-time": now,
+                # "operational-events": [],   # "id", "timestamp", "description", "event",
+                # "ssh-authorized-key": slice_request.get("key-pair-ref"),
+            }
+            # nstd["nsi_id"] = nsi_id
+
+            # TODO: ask if we have to develop the VNFR here or we can imply call the NsrTopic() for each service to 
+            # instantiate.
+            # Create netslice-subnet_record
+            needed_nsds = {}
+            for member_ns in nstd["netslice-subnet"]:
+                nsd_id = member_ns["nsd-ref"]
+                step = "getting nstd id='{}' constituent-nsd='{}' from database".format(
+                    member_ns["nsd-ref"], member_ns["id"])
+                if nsd_id not in needed_nsds:
+                    # Obtain nsd
+                    nsd = DescriptorTopic.get_one_by_id(self.db, session, "nsds", nsd_id)
+                    nsd.pop("_admin")
+                    needed_nsds[nsd_id] = nsd
+                else:
+                    nsd = needed_nsds[nsd_id]
+                
+                step = "filling nsir nsd-id='{}' constituent-nsd='{}' from database".format(
+                    member_ns["nsd-ref"], member_ns["id"])
+                netslice_subnet_descriptor = {
+                    "nsName": member_ns["instantiation-parameters"]["name"],
+                    "nsdId": member_ns["instantiation-parameters"]["nsdId"],
+                    "vimAccountId": member_ns["instantiation-parameters"]["vimAccountId"]
+                }
+                nsi_descriptor["instantiation-parameters"]["netslice-subnet"].append(netslice_subnet_descriptor)
+
+            step = "creating nsi at database"
+            self.format_on_new(nsi_descriptor, session["project_id"], make_public=make_public)
+            self.db.create("nsis", nsi_descriptor)
+            rollback.append({"topic": "nsis", "_id": nsi_id})
+            return nsi_id
+        except Exception as e:
+            self.logger.exception("Exception {} at NsiTopic.new()".format(e), exc_info=True)
+            raise EngineException("Error {}: {}".format(step, e))
+        except ValidationError as e:
+            raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
+
+    def edit(self, session, _id, indata=None, kwargs=None, force=False, content=None):
+        raise EngineException("Method edit called directly", HTTPStatus.INTERNAL_SERVER_ERROR)
index aeff32c..e8f2246 100644 (file)
@@ -661,7 +661,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"):
+            if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "nst", "nsilcm"):
                 raise NbiException("URL main_topic '{}' not supported".format(main_topic),
                                    HTTPStatus.METHOD_NOT_ALLOWED)
             if version != 'v1':
@@ -705,14 +705,15 @@ class Server(object):
             elif main_topic == "nsilcm":
                 engine_topic = "nsis"
                 if topic == "nsi_lcm_op_occs":
-                    engine_topic = "nsilcmops"
+                    engine_topic = "nsilcmops" 
             elif main_topic == "pdu":
                 engine_topic = "pdus"
             if engine_topic == "vims":   # TODO this is for backward compatibility, it will remove in the future
                 engine_topic = "vim_accounts"
 
             if method == "GET":
-                if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
+                if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content", 
+                            "netslice_instances"):
                     if item in ("vnfd", "nsd", "nst"):
                         path = "$DESCRIPTOR"
                     elif args:
diff --git a/osm_nbi/tests/cirros_slice/cirros_slice.yaml b/osm_nbi/tests/cirros_slice/cirros_slice.yaml
new file mode 100644 (file)
index 0000000..3b5c963
--- /dev/null
@@ -0,0 +1,21 @@
+nst:
+    nst:
+    -   id: cirros_nst
+        name: cirros_slice_template
+        SNSSAI-identifier:
+        -   slice-service-type: eMBB
+        quality-of-service:
+        -   id: 1
+        netslice-subnet:
+        -   id: cirros_nsd_nst
+            name: cirros_ns
+            short-name: cirros_ns
+            vendor: OSM
+            is-shared-nss: 'false'
+            description: Slice for osm-3.0-three 2nd-hackfest nsd2vnfs package
+            version: '1.0'
+            nsd-ref: cirros_2vnf_nsd
+            instantiation-parameters:
+            -   name: cirros_2vnf_nsd
+                nsdId: cirros_2vnf_nsd
+                vimAccountId: openstack_18
\ No newline at end of file
index e53df12..5ba9d4b 100755 (executable)
@@ -66,7 +66,7 @@ then
     done
 fi
 
-for item in vim_accounts sdns nsrs vnfrs nslcmops nsds vnfds projects # vims
+for item in vim_accounts sdns nsrs vnfrs nslcmops nsds vnfds projects nsts # vims
 do
     curl --insecure ${OSMNBI_URL}/test/db-clear/${item}
     echo " ${item}"
index 0c08c22..bcdfadf 100755 (executable)
@@ -1334,6 +1334,33 @@ class TestDescriptors:
         self.step += 1
 
 
+class TestNstTemplates:
+    description = "Upload a NST to OSM"
+
+    def __init__(self):
+        self.nst_filenames = ("@./cirros_slice/cirros_slice.yaml")
+
+    def run(self, engine, test_osm, manual_check, test_params=None):
+        # nst CREATE
+        engine.get_autorization()
+        r = engine.test("NST", "Onboard NST", "POST", "/nst/v1/netslice_templates_content", headers_yaml, 
+                        self.nst_filenames, 
+                        201, {"Location": "/nst/v1/netslice_templates_content", "Content-Type": "application/yaml"}, 
+                        "yaml")
+        location = r.headers["Location"]
+        nst_id = location[location.rfind("/")+1:]
+
+        # nstd SHOW OSM format
+        r = engine.test("NST", "Show NSTD OSM format", "GET", 
+                        "/nst/v1/netslice_templates_content/{}".format(nst_id), headers_json, None, 
+                        200, r_header_json, "json")      
+
+        # nstd DELETE
+        r = engine.test("NST", "Delete NSTD", "DELETE", 
+                        "/nst/v1/netslice_templates_content/{}".format(nst_id), headers_json, None, 
+                        204, None, 0)
+
+
 if __name__ == "__main__":
     global logger
     test = ""
@@ -1368,6 +1395,7 @@ if __name__ == "__main__":
             "TestDescriptors": TestDescriptors,
             "TestDeployHackfest1": TestDeployHackfest1,
             # "Deploy-MultiVIM": TestDeployMultiVIM,
+            "Upload-Slice-Template": TestNstTemplates,
         }
         test_to_do = []
         test_params = {}