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"
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
map_from_topic_to_class = {
"vnfds": VnfdTopic,
"nsds": NsdTopic,
+ "nsts": NstTopic,
"pdus": PduTopic,
"nsrs": NsrTopic,
"vnfrs": VnfrTopic,
"sdns": SdnTopic,
"users": UserTopic,
"projects": ProjectTopic,
+ "nsis": NsiTopic,
# [NEW_TOPIC]: add an entry here
}
<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>
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)
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)
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':
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:
--- /dev/null
+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
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}"
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 = ""
"TestDescriptors": TestDescriptors,
"TestDeployHackfest1": TestDeployHackfest1,
# "Deploy-MultiVIM": TestDeployMultiVIM,
+ "Upload-Slice-Template": TestNstTemplates,
}
test_to_do = []
test_params = {}