From e47b913f0500837d7caa2ee25c0b476f58884859 Mon Sep 17 00:00:00 2001 From: almagia Date: Tue, 17 May 2022 14:12:22 +0200 Subject: [PATCH] Revert "Revert "Feature 10915: SOL003 STD Support for OSM"" This reverts commit b1186b0b9ea287010c9f37ba6062512ea93f0ed4. As requested by TSC Chair Change-Id: I811ceec9e7d458b9a528f131cf44c2807f45efb2 --- osm_nbi/engine.py | 3 + osm_nbi/nbi.py | 36 +++ osm_nbi/osm_vnfm/__init__.py | 14 + osm_nbi/osm_vnfm/base_methods.py | 29 +++ osm_nbi/osm_vnfm/vnf_instance_actions.py | 226 +++++++++++++++++ osm_nbi/osm_vnfm/vnf_instances.py | 309 +++++++++++++++++++++++ osm_nbi/resources_to_operations.yml | 6 + osm_nbi/tests/test_db_descriptors.py | 145 +++++++++++ osm_nbi/tests/test_osm_vnfm.py | 227 +++++++++++++++++ osm_nbi/vnf_instance_topics.py | 116 +++++++++ 10 files changed, 1111 insertions(+) create mode 100644 osm_nbi/osm_vnfm/__init__.py create mode 100644 osm_nbi/osm_vnfm/base_methods.py create mode 100644 osm_nbi/osm_vnfm/vnf_instance_actions.py create mode 100644 osm_nbi/osm_vnfm/vnf_instances.py create mode 100644 osm_nbi/tests/test_osm_vnfm.py create mode 100644 osm_nbi/vnf_instance_topics.py diff --git a/osm_nbi/engine.py b/osm_nbi/engine.py index cb27414..7afaa22 100644 --- a/osm_nbi/engine.py +++ b/osm_nbi/engine.py @@ -52,6 +52,7 @@ from osm_nbi.instance_topics import ( NsiTopic, NsiLcmOpTopic, ) +from osm_nbi.vnf_instance_topics import VnfInstances, VnfLcmOpTopic from osm_nbi.pmjobs_topics import PmJobsTopic from osm_nbi.subscription_topics import NslcmSubscriptionsTopic from base64 import b64encode @@ -85,6 +86,8 @@ class Engine(object): "nsilcmops": NsiLcmOpTopic, "vnfpkgops": VnfPkgOpTopic, "nslcm_subscriptions": NslcmSubscriptionsTopic, + "vnf_instances": VnfInstances, + "vnflcmops": VnfLcmOpTopic, # [NEW_TOPIC]: add an entry here # "pm_jobs": PmJobsTopic will be added manually because it needs other parameters } diff --git a/osm_nbi/nbi.py b/osm_nbi/nbi.py index 29fcacd..ff2f6de 100644 --- a/osm_nbi/nbi.py +++ b/osm_nbi/nbi.py @@ -482,6 +482,31 @@ valid_url_methods = { }, } }, + "vnflcm": { + "v1": { + "vnf_instances": {"METHODS": ("GET", "POST"), + "ROLE_PERMISSION": "vnflcm_instances:", + "": {"METHODS": ("GET", "DELETE"), + "ROLE_PERMISSION": "vnflcm_instances:id:", + "scale": {"METHODS": ("POST",), + "ROLE_PERMISSION": "vnflcm_instances:id:scale:" + }, + "terminate": {"METHODS": ("POST",), + "ROLE_PERMISSION": "vnflcm_instances:id:terminate:" + }, + "instantiate": {"METHODS": ("POST",), + "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:" + }, + } + }, + "vnf_lcm_op_occs": {"METHODS": ("GET",), + "ROLE_PERMISSION": "vnf_instances:opps:", + "": {"METHODS": ("GET",), + "ROLE_PERMISSION": "vnf_instances:opps:id:" + }, + }, + } + }, "nst": { "v1": { "netslice_templates_content": { @@ -1298,6 +1323,7 @@ class Server(object): "nst", "nsilcm", "nspm", + "vnflcm", ): raise NbiException( "URL main_topic '{}' not supported".format(main_topic), @@ -1352,6 +1378,9 @@ class Server(object): engine_topic = "nslcmops" if topic == "vnfrs" or topic == "vnf_instances": engine_topic = "vnfrs" + elif main_topic == "vnflcm": + if topic == "vnf_lcm_op_occs": + engine_topic = "vnflcmops" elif main_topic == "nst": engine_topic = "nsts" elif main_topic == "nsilcm": @@ -1509,6 +1538,13 @@ class Server(object): "_links": link, } cherrypy.response.status = HTTPStatus.CREATED.value + elif topic == "vnf_instances" and item: + indata["lcmOperationType"] = item + indata["vnfInstanceId"] = _id + _id, _ = self.engine.new_item(rollback, engine_session, "vnflcmops", indata, kwargs) + self._set_location_header(main_topic, version, "vnf_lcm_op_occs", _id) + outdata = {"id": _id} + cherrypy.response.status = HTTPStatus.ACCEPTED.value else: _id, op_id = self.engine.new_item( rollback, diff --git a/osm_nbi/osm_vnfm/__init__.py b/osm_nbi/osm_vnfm/__init__.py new file mode 100644 index 0000000..b62a185 --- /dev/null +++ b/osm_nbi/osm_vnfm/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 K Sai Kiran (Tata Elxsi) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/osm_nbi/osm_vnfm/base_methods.py b/osm_nbi/osm_vnfm/base_methods.py new file mode 100644 index 0000000..f7ca5f4 --- /dev/null +++ b/osm_nbi/osm_vnfm/base_methods.py @@ -0,0 +1,29 @@ +# Copyright 2021 K Sai Kiran (Tata Elxsi) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = "K Sai Kiran , Selvi Jayaraman " +__date__ = "$12-June-2021 8:30:59$" + + +import logging + + +class BaseMethod: + + def __init__(self): + """ + Constructor of the base method + """ + self.logger = logging.getLogger("nbi.engine") diff --git a/osm_nbi/osm_vnfm/vnf_instance_actions.py b/osm_nbi/osm_vnfm/vnf_instance_actions.py new file mode 100644 index 0000000..93c91c5 --- /dev/null +++ b/osm_nbi/osm_vnfm/vnf_instance_actions.py @@ -0,0 +1,226 @@ +# Copyright 2021 K Sai Kiran (Tata Elxsi) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = "K Sai Kiran , Selvi Jayaraman " +__date__ = "$12-June-2021 8:30:59$" + +from osm_nbi.instance_topics import NsrTopic, NsLcmOpTopic, VnfrTopic +from .base_methods import BaseMethod + + +class VnfLcmOp2NsLcmOp: + + def __init__(self, db, fs, msg, auth): + """ + Constructor of Vnf lcm op to Ns lcm op + """ + self.new_vnf_lcmop = NewVnfLcmOp(db, fs, msg, auth) + self.list_vnf_lcmop = ListVnfLcmOp(db, fs, msg, auth) + self.show_vnf_lcmop = ShowVnfLcmOp(db, fs, msg, auth) + + def new(self, rollback, session, indata=None, kwargs=None, headers=None): + """ + Creates a new entry into database. + :param rollback: list to append created items at database in case a rollback may to be done + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param indata: data to be inserted + :param kwargs: used to override the indata descriptor + :param headers: http request headers + :return: _id, op_id: + _id: identity of the inserted data. + op_id: operation id if this is asynchronous, None otherwise + """ + return self.new_vnf_lcmop.action(rollback, session, indata, kwargs, headers) + + def list(self, session, filter_q=None, api_req=False): + """ + Get a list of the Vnf Lcm Operation that matches a filter + :param session: contains the used login username and working project + :param filter_q: filter of data to be applied + :param api_req: True if this call is serving an external API request. False if serving internal request. + :return: The list, it can be empty if no one match the filter. + """ + return self.list_vnf_lcmop.action(session, filter_q, api_req) + + def show(self, session, _id, api_req=False): + """ + Get complete information on an Vnf Lcm Operation + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param _id: server internal id + :param api_req: True if this call is serving an external API request. False if serving internal request. + :return: dictionary, raise exception if not found. + """ + return self.show_vnf_lcmop.action(session, _id, api_req) + + +class NewVnfLcmOp(BaseMethod): + + def __init__(self, db, fs, msg, auth): + """ + Constructor of new Vnf Lcm Op + """ + super().__init__() + self.msg = msg + self.nslcmoptopic = NsLcmOpTopic(db, fs, msg, auth) + self.nsrtopic = NsrTopic(db, fs, msg, auth) + self.vnfrtopic = VnfrTopic(db, fs, msg, auth) + + def __get_nsdid(self, session, vnf_instance_id): + """ + Returns a nsd id from vnf instance id. + :param session: contains the used login username and working project + :param vnf_instance_id: id of vnf instance + :return: id of nsd id + """ + nsr = self.nsrtopic.show(session, vnf_instance_id) + return nsr['nsd']['_id'] + + def __get_formatted_indata(self, session, indata): + """ + Returns formatted data for new vnf lcm op + :param session: contains the used login username and working project + :param indata: contains information for new lcm operation. + :return: formatted indata for new lcm op. + """ + formatted_indata = {} + if indata["lcmOperationType"] == "instantiate": + formatted_indata = { + "nsName": indata["vnfName"], + "nsDescription": indata["vnfDescription"], + "nsdId": self.__get_nsdid(session, indata["vnfInstanceId"]), + "vimAccountId": indata["vimAccountId"], + "nsr_id": indata["vnfInstanceId"], + "lcmOperationType": indata["lcmOperationType"], + "nsInstanceId": indata["vnfInstanceId"] + } + elif indata["lcmOperationType"] == "terminate": + formatted_indata = { + "lcmOperationType": indata["lcmOperationType"], + "nsInstanceId": indata["vnfInstanceId"] + } + elif indata["lcmOperationType"] == "scale": + formatted_indata = { + "lcmOperationType": indata["lcmOperationType"], + "nsInstanceId": indata["vnfInstanceId"], + "scaleType": "SCALE_VNF", + "scaleVnfData": { + "scaleVnfType": indata["type"], + "scaleByStepData": { + "scaling-group-descriptor": indata["aspectId"], + "member-vnf-index": indata["additionalParams"]["member-vnf-index"] + } + } + } + elif indata["lcmOperationType"] == "action": + formatted_indata = { + "lcmOperationType": indata["lcmOperationType"], + "nsInstanceId": indata["vnfInstanceId"], + "member_vnf_index": indata["member_vnf_index"], + "primitive": indata["primitive"], + "primitive_params": indata["primitive_params"] + } + return formatted_indata + + def action(self, rollback, session, indata=None, kwargs=None, headers=None): + """ + Creates an new lcm operation. + :param rollback: list to append the created items at database in case a rollback must be done + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param indata: params to be used for the nsr + :param kwargs: used to override the indata + :param headers: http request headers + :return: id of new lcm operation. + """ + vnfInstanceId = indata["vnfInstanceId"] + lcm_operation = indata["lcmOperationType"] + vnfr = self.vnfrtopic.show(session, vnfInstanceId) + indata["vnfInstanceId"] = vnfr.get("nsr-id-ref") + indata = self.__get_formatted_indata(session, indata) + op_id, _ = self.nslcmoptopic.new(rollback, session, indata, kwargs, headers) + return op_id, _ + + +class ListVnfLcmOp(BaseMethod): + + def __init__(self, db, fs, msg, auth): + """ + Constructor call for listing vnf lcm operations + """ + super().__init__() + self.nslcmoptopic = NsLcmOpTopic(db, fs, msg, auth) + self.nsrtopic = NsrTopic(db, fs, msg, auth) + + def action(self, session, filter_q=None, api_req=False): + """ + To get list of vnf lcm operations that matches a filter + :param session: contains the used login username and working project + :param filter_q: filter of data to be applied + :param api_req: True if this call is serving an external API request. False if serving internal request. + :return: The list, it can be empty if no one match the filter. + """ + list = [] + records = self.nslcmoptopic.list(session, filter_q, api_req) + for record in records: + ns_id = record.get("nsInstanceId") + nsr = self.nsrtopic.show(session, ns_id) + vnfInstance_id = nsr['constituent-vnfr-ref'][0] + outdata = sol003_projection(record, vnfInstance_id) + list.append(outdata) + return list + + +class ShowVnfLcmOp(BaseMethod): + + def __init__(self, db, fs, msg, auth): + """ + Constructor call for showing vnf lcm operation + """ + super().__init__() + self.nslcmoptopic = NsLcmOpTopic(db, fs, msg, auth) + self.nsrtopic = NsrTopic(db, fs, msg, auth) + + def action(self, session, _id, api_req=False): + """ + Get complete information on an Vnf Lcm Operation. + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param _id: Vnf Lcm operation id + :param api_req: True if this call is serving an external API request. False if serving internal request. + :return: dictionary, raise exception if not found. + """ + record = self.nslcmoptopic.show(session, _id, api_req) + ns_id = record.get("nsInstanceId") + nsr = self.nsrtopic.show(session, ns_id) + vnfinstance_id = nsr['constituent-vnfr-ref'][0] + outdata = sol003_projection(record, vnfinstance_id) + return outdata + + +def sol003_projection(data, vnfinstance_id): + """ + Returns SOL003 formatted data + :param data: contains Lcm Operation information + :param vnfinstance_id: id of vnf_instance + :return: SOL003 formatted data of vnf lcm op + """ + data.pop("nsInstanceId") + data.pop("operationParams") + data.pop("links") + links = { + "self": "/osm/vnflcm/v1/vnf_lcm_op_occs/" + data["_id"], + "vnfInstance": "/osm/vnflcm/v1/vnf_instances/" + vnfinstance_id, + } + data["_links"] = links + data["vnfInstanceId"] = vnfinstance_id + return data diff --git a/osm_nbi/osm_vnfm/vnf_instances.py b/osm_nbi/osm_vnfm/vnf_instances.py new file mode 100644 index 0000000..a41f6d5 --- /dev/null +++ b/osm_nbi/osm_vnfm/vnf_instances.py @@ -0,0 +1,309 @@ +# Copyright 2021 K Sai Kiran (Tata Elxsi) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = "K Sai Kiran , Selvi Jayaraman " +__date__ = "$12-June-2021 8:30:59$" + +from copy import deepcopy +from osm_nbi.descriptor_topics import NsdTopic +from .base_methods import BaseMethod + +from osm_nbi.instance_topics import NsrTopic, VnfrTopic + + +class VnfInstances2NsInstances: + + def __init__(self, db, fs, msg, auth): + """ + Constructor of Vnf Instances to Ns Instances + """ + self.new_vnf_instance = NewVnfInstance(db, fs, msg, auth) + self.list_vnf_instance = ListVnfInstance(db, fs, msg, auth) + self.show_vnf_instance = ShowVnfInstance(db, fs, msg, auth) + self.delete_vnf_instance = DeleteVnfInstance(db, fs, msg, auth) + + def new(self, rollback, session, indata=None, kwargs=None, headers=None): + """ + Creates a new entry into database. + :param rollback: list to append created items at database in case a rollback may have to be done + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param indata: data to be inserted + :param kwargs: used to override the indata descriptor + :param headers: http request headers + :return: _id, op_id: + _id: identity of the inserted data. + op_id: operation id if this is asynchronous, None otherwise + """ + return self.new_vnf_instance.action(rollback, session, indata, kwargs, headers) + + def list(self, session, filter_q=None, api_req=False): + """ + Get a list of the Vnfs that match a filter + :param session: contains the used login username and working project + :param filter_q: filter of data to be applied + :param api_req: True if this call is serving an external API request. False if serving internal request. + :return: The list, it can be empty if no one match the filter. + """ + return self.list_vnf_instance.action(session, filter_q, api_req) + + def show(self, session, _id, api_req=False): + """ + Get complete information on an Vnf + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param _id: server internal id + :param api_req: True if this call is serving an external API request. False if serving internal request. + :return: dictionary, raise exception if not found. + """ + return self.show_vnf_instance.action(session, _id, api_req) + + def delete(self, session, _id, dry_run=False, not_send_msg=None): + """ + Delete item by its internal _id + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param _id: server internal id + :param dry_run: make checking but do not delete + :param not_send_msg: To not send message (False) or store content (list) instead + :return: operation id (None if there is not operation), raise exception if error or not found, conflict, ... + """ + return self.delete_vnf_instance.action(session, _id, dry_run, not_send_msg) + + +class NewVnfInstance(BaseMethod): + + # sample ns descriptor + sample_nsd = { + "nsd": { + "nsd": [ + { + "df": [ + { + "id": "default-df", + "vnf-profile": [ + { + "id": 1, + "virtual-link-connectivity": [ + { + "constituent-cpd-id": [ + {"constituent-base-element-id": 1, + "constituent-cpd-id": "eth0-ext"} + ], + "virtual-link-profile-id": "mgmtnet", + } + ], + "vnfd-id": "cirros_vnfd" + } + ], + } + ], + "vnfd-id": ["cirros_vnfd"], + "description": "Generated by OSM pacakage generator", + "id": "cirros_2vnf_nsd", + "name": "cirros_2vnf_ns", + "short-name": "cirros_2vnf_ns", + "vendor": "OSM", + "version": "1.0", + } + ] + } + } + + @staticmethod + def __get_formatted_indata(indata, nsd_id): + """ + Create indata for nsd_id + :param indata: Contains unformatted data for new vnf instance + :param nsd_id: Id of nsd + :return: formatted indata for nsd_id + """ + formatted_indata = deepcopy(indata) + formatted_indata["nsdId"] = nsd_id + formatted_indata["nsName"] = indata["vnfInstanceName"] + "-ns" + for invalid_key in ("vnfdId", "vnfInstanceName", "vnfInstanceDescription", "additionalParams"): + formatted_indata.pop(invalid_key) + return formatted_indata + + def __init__(self, db, fs, msg, auth): + """ + Constructor for new vnf instance + """ + super().__init__() + self.msg = msg + self.nsdtopic = NsdTopic(db, fs, msg, auth) + self.nsrtopic = NsrTopic(db, fs, msg, auth) + + def __get_vnfd(self): + # get vnfd from nfvo + pass + + def __onboard_vnfd(self): + self.__get_vnfd() + pass + + def __create_nsd(self, rollback, session, indata=None, kwargs=None, headers=None): + """ + Creates new ns descriptor from a vnfd. + :param rollback: list to append the created items at database in case a rollback must be done + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param indata: params to be used for the nsr + :param kwargs: used to override the indata + :param headers: http request headers + :return: id of new nsd created + """ + _id, *others = self.nsdtopic.new(rollback, session, {}, None, headers) + new_nsd = deepcopy(NewVnfInstance.sample_nsd) + vnf_content = { + "id":"default-df", + "vnf-profile": [ + { + "id": "1", + "virtual-link-connectivity": [ + { + "constituent-cpd-id": [ + { + "constituent-base-element-id": "1", + "constituent-cpd-id": indata["additionalParams"]["constituent-cpd-id"] + } + ], + "virtual-link-profile-id": indata["additionalParams"]["virtual-link-profile-id"] + } + ], + "vnfd-id": indata["vnfdId"] + } + ] + } + new_nsd["nsd"]["nsd"][0] = { + "description": indata["vnfInstanceDescription"], + "designer": "OSM", + "id": indata["vnfdId"] + "-ns", + "name": indata["vnfdId"] + "-ns", + "version": "1.0", + "df": [vnf_content, ], + "virtual-link-desc": indata["additionalParams"]["virtual-link-desc"], + "vnfd-id": [indata["vnfdId"]] + } + return _id, new_nsd + + def __create_nsr(self, rollback, session, indata=None, kwargs=None, headers=None): + """ + Creates an new ns record in database + :param rollback: list to append the created items at database in case a rollback must be done + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param indata: params to be used for the nsr + :param kwargs: used to override the indata + :param headers: http request headers + :return: id of new nsr + """ + return self.nsrtopic.new(rollback, session, indata, kwargs, headers) + + def __action_pre_processing(self, rollback, session, indata=None, kwargs=None, headers=None): + """ + Pre process for creating new vnf instance + :param rollback: list to append the created items at database in case a rollback must be done + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param indata: params to be used for the nsr + :param kwargs: used to override the indata + :param headers: http request headers + :return: id nsr + """ + self.__onboard_vnfd() + nsd_id, nsd = self.__create_nsd(rollback, session, indata, kwargs, headers) + self.nsdtopic.upload_content(session, nsd_id, nsd, kwargs, headers) + formatted_indata = NewVnfInstance.__get_formatted_indata(indata, nsd_id) + nsr_id, _ = self.__create_nsr(rollback, session, formatted_indata, kwargs, headers) + nsr = self.nsrtopic.show(session, nsr_id) + vnfr_id = nsr['constituent-vnfr-ref'][0] + return vnfr_id, None + + def action(self, rollback, session, indata=None, kwargs=None, headers=None): + """ + Creates an new vnf instance + :param rollback: list to append the created items at database in case a rollback must be done + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param indata: params to be used for the nsr + :param kwargs: used to override the indata + :param headers: http request headers + :return: id of new vnf instance + """ + return self.__action_pre_processing(rollback, session, indata, kwargs, headers) + + +class ListVnfInstance(BaseMethod): + + def __init__(self, db, fs, msg, auth): + """ + Constructor call for listing vnfs + """ + super().__init__() + self.vnfrtopic = VnfrTopic(db, fs, msg, auth) + + def action(self, session, filter_q=None, api_req=False): + """ + To get list of vnfs that matches a filter + :param session: contains the used login username and working project + :param filter_q: filter of data to be applied + :param api_req: True if this call is serving an external API request. False if serving internal request. + :return: The list, it can be empty if no one match the filter. + """ + return self.vnfrtopic.list(session, filter_q, api_req) + + +class ShowVnfInstance(BaseMethod): + + def __init__(self, db, fs, msg, auth): + """ + Constructor call for showing vnf lcm operation + """ + super().__init__() + self.vnfrtopic = VnfrTopic(db, fs, msg, auth) + + def action(self, session, _id, api_req=False): + """ + Get complete information on an Vnf Lcm Operation. + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param _id: Vnf Lcm operation id + :param api_req: True if this call is serving an external API request. False if serving internal request. + :return: dictionary, raise exception if not found. + """ + return self.vnfrtopic.show(session, _id, api_req) + + +class DeleteVnfInstance(BaseMethod): + + def __init__(self, db, fs, msg, auth): + """ + Constructor call for deleting vnf + """ + super().__init__() + self.msg = msg + self.nsrtopic = NsrTopic(db, fs, msg, auth) + self.nsdtopic = NsdTopic(db, fs, msg, auth) + self.vnfrtopic = VnfrTopic(db, fs, msg, auth) + + def action(self, session, _id, dry_run=False, not_send_msg=None): + """ + Delete vnf instance by its internal _id + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param _id: server internal id + :param dry_run: make checking but do not delete + :param not_send_msg: To not send message (False) or store content (list) instead + :return: operation id (None if there is not operation), raise exception if error or not found, conflict, ... + """ + vnfInstanceId = _id + vnfr = self.vnfrtopic.show(session, vnfInstanceId) + ns_id = vnfr.get("nsr-id-ref") + nsr = self.nsrtopic.show(session, ns_id) + nsd_to_del = nsr['nsd']['_id'] + self.nsrtopic.delete(session, ns_id, dry_run, not_send_msg) + return self.nsdtopic.delete(session, nsd_to_del, dry_run, not_send_msg) diff --git a/osm_nbi/resources_to_operations.yml b/osm_nbi/resources_to_operations.yml index 8880817..1f75a5b 100644 --- a/osm_nbi/resources_to_operations.yml +++ b/osm_nbi/resources_to_operations.yml @@ -149,6 +149,12 @@ resources_to_operations: "GET /nslcm/v1/vnfrs/": "vnf_instances:id:get" "GET /nslcm/v1/vnf_instances/": "vnf_instances:id:get" + "GET /vnflcm/v1/vnf_instances/": "vnflcm_instances:get" + "POST /vnflcm/v1/vnf_instances/": "vnflcm_instances:post" + + "GET /vnflcm/v1/vnf_instances/": "vnflcm_instances:id:get" + "DELETE /vnflcm/v1/vnf_instances/": "vnflcm_instances:id:delete" + ################################################################################ #################################### Tokens #################################### ################################################################################ diff --git a/osm_nbi/tests/test_db_descriptors.py b/osm_nbi/tests/test_db_descriptors.py index 8b0b226..ea3cdd8 100644 --- a/osm_nbi/tests/test_db_descriptors.py +++ b/osm_nbi/tests/test_db_descriptors.py @@ -623,3 +623,148 @@ db_vnfrs_text = """ vnfd-id: 7637bcf8-cf14-42dc-ad70-c66fcf1e6e77 vnfd-ref: hackfest3charmed-vnf """ + +db_vnfm_vnfd_text = """ +--- +- _admin: + created: 1647529096.3635302 + modified: 1650456936.518325 + onboardingState: ONBOARDED + operationalState: ENABLED + projects_read: + - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4 + projects_write: + - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4 + storage: + descriptor: hackfest_basic_metrics_vnf/hackfest_basic_metrics_vnfd.yaml + folder: 70b47595-fafa-4f63-904b-fc3ada60eebb + fs: mongo + path: /app/storage/ + pkg-dir: hackfest_basic_metrics_vnf + zipfile: package.tar.gz + type: vnfd + usageState: NOT_IN_USE + userDefinedData: {} + _id: 70b47595-fafa-4f63-904b-fc3ada60eebb + _links: + packageContent: + href: /vnfpkgm/v1/vnf_packages/70b47595-fafa-4f63-904b-fc3ada60eebb/package_content + self: + href: /vnfpkgm/v1/vnf_packages/70b47595-fafa-4f63-904b-fc3ada60eebb + vnfd: + href: /vnfpkgm/v1/vnf_packages/70b47595-fafa-4f63-904b-fc3ada60eebb/vnfd + description: A basic VNF descriptor with one VDU and VIM metrics + df: + - id: default-df + instantiation-level: + - id: default-instantiation-level + vdu-level: + - number-of-instances: 1 + vdu-id: hackfest_basic_metrics-VM + scaling-aspect: + - aspect-delta-details: + deltas: + - id: vdu_autoscale-delta + vdu-delta: + - id: hackfest_basic_metrics-VM + number-of-instances: 1 + id: vdu_autoscale + max-scale-level: 1 + name: vdu_autoscale + scaling-policy: + - cooldown-time: 120 + name: cpu_util_above_threshold + scaling-criteria: + - name: cpu_util_above_threshold + scale-in-relational-operation: LT + scale-in-threshold: '10.0000000000' + scale-out-relational-operation: GT + scale-out-threshold: '60.0000000000' + vnf-monitoring-param-ref: vnf_cpu_util + scaling-type: automatic + threshold-time: 10 + vdu-profile: + - id: hackfest_basic_metrics-VM + max-number-of-instances: 2 + min-number-of-instances: 1 + ext-cpd: + - id: vnf-cp0-ext + int-cpd: + cpd: vdu-eth0-int + vdu-id: hackfest_basic_metrics-VM + id: hackfest_basic_metrics-vnf + mgmt-cp: vnf-cp0-ext + onboardingState: ONBOARDED + operationalState: ENABLED + product-name: hackfest_basic_metrics-vnf + sw-image-desc: + - id: bionic + image: bionic + name: bionic + - id: ubuntu18.04-aws + image: ubuntu/images/hvm-ssd/ubuntu-artful-17.10-amd64-server-20180509 + name: ubuntu18.04-aws + vim-type: aws + - id: ubuntu18.04-azure + image: Canonical:UbuntuServer:18.04-LTS:latest + name: ubuntu18.04-azure + vim-type: azure + - id: ubuntu18.04-gcp + image: ubuntu-os-cloud:image-family:ubuntu-1804-lts + name: ubuntu18.04-gcp + vim-type: gcp + usageState: NOT_IN_USE + vdu: + - alarm: + - actions: + alarm: + - url: https://webhook.site/b79f9bf9-4c19-429d-81ed-19be26a3d5d8 + insufficient-data: + - url: https://webhook.site/b79f9bf9-4c19-429d-81ed-19be26a3d5d8 + ok: + - url: https://webhook.site/b79f9bf9-4c19-429d-81ed-19be26a3d5d8 + alarm-id: alarm-1 + operation: LT + value: '20.0000' + vnf-monitoring-param-ref: vnf_cpu_util + alternative-sw-image-desc: + - ubuntu18.04-aws + - ubuntu18.04-azure + - ubuntu18.04-gcp + cloud-init-file: cloud-config + id: hackfest_basic_metrics-VM + int-cpd: + - id: vdu-eth0-int + virtual-network-interface-requirement: + - name: vdu-eth0 + virtual-interface: + type: PARAVIRT + monitoring-parameter: + - id: vnf_cpu_util + name: vnf_cpu_util + performance-metric: cpu_utilization + - id: vnf_memory_util + name: vnf_memory_util + performance-metric: average_memory_utilization + - id: vnf_packets_sent + name: vnf_packets_sent + performance-metric: packets_sent + - id: vnf_packets_received + name: vnf_packets_received + performance-metric: packets_received + name: hackfest_basic_metrics-VM + sw-image-desc: bionic + virtual-compute-desc: hackfest_basic_metrics-VM-compute + virtual-storage-desc: + - hackfest_basic_metrics-VM-storage + version: '1.0' + virtual-compute-desc: + - id: hackfest_basic_metrics-VM-compute + virtual-cpu: + num-virtual-cpu: 1 + virtual-memory: + size: 1.0 + virtual-storage-desc: + - id: hackfest_basic_metrics-VM-storage + size-of-storage: '10' +""" diff --git a/osm_nbi/tests/test_osm_vnfm.py b/osm_nbi/tests/test_osm_vnfm.py new file mode 100644 index 0000000..a7ea8a3 --- /dev/null +++ b/osm_nbi/tests/test_osm_vnfm.py @@ -0,0 +1,227 @@ +# Copyright 2021 Selvi Jayaraman (Tata Elxsi) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = "Selvi Jayaraman " + +import unittest +from uuid import uuid4 +from unittest.mock import Mock, patch, mock_open +from osm_common.dbmemory import DbMemory +from osm_common.fsbase import FsBase +from osm_common.msgbase import MsgBase +from osm_nbi.vnf_instance_topics import VnfInstances, VnfLcmOpTopic +from osm_nbi.instance_topics import NsrTopic +from osm_nbi.tests.test_db_descriptors import ( + db_vim_accounts_text, + db_vnfm_vnfd_text, + db_nsds_text, + db_nsrs_text, + db_vnfrs_text, + db_nslcmops_text +) +import yaml + + +class TestVnfInstances(unittest.TestCase): + def setUp(self): + self.db = DbMemory() + self.fs = Mock(FsBase()) + self.msg = Mock(MsgBase()) + self.vnfinstances = VnfInstances(self.db, self.fs, self.msg, None) + self.nsrtopic = NsrTopic(self.db, self.fs, self.msg, None) + self.db.create_list( + "vim_accounts", yaml.load(db_vim_accounts_text, Loader=yaml.Loader) + ) + self.db.create_list("vnfds", yaml.load(db_vnfm_vnfd_text, Loader=yaml.Loader)) + self.vnfd = self.db.get_list("vnfds")[0] + self.vnfd_id = self.vnfd["id"] + self.vnfd_project = self.vnfd["_admin"]["projects_read"][0] + + self.vim = self.db.get_list("vim_accounts")[0] + self.vim_id = self.vim["_id"] + + @patch("osm_nbi.descriptor_topics.shutil") + @patch("osm_nbi.descriptor_topics.os.rename") + def test_create_identifier(self, mock_rename, mock_shutil): + session = { + "force": True, + "admin": False, + "public": False, + "project_id": [self.vnfd_project], + "method": "write", + } + indata = { + "vnfdId": self.vnfd_id, + "vnfInstanceName": "vnf_instance_name", + "vnfInstanceDescription": "vnf instance description", + "vimAccountId": self.vim_id, + "additionalParams": { + "virtual-link-desc": [ + { + "id": "mgmt-net", + "mgmt-network": True + } + ], + "constituent-cpd-id": "vnf-cp0-ext", + "virtual-link-profile-id": "mgmt-net" + } + } + rollback = [] + headers = {} + self.fs.path = "" + self.fs.get_params.return_value = {} + self.fs.file_exists.return_value = False + self.fs.file_open.side_effect = lambda path, mode: open( + "/tmp/" + str(uuid4()), "a+b" + ) + + vnfr_id, _ = self.vnfinstances.new( + rollback, session, indata, {}, headers={"Content-Type": []} + ) + vnfr = self.db.get_one("vnfrs") + self.assertEqual( + vnfr_id, + vnfr["id"], + "Mismatch between return id and database id" + ) + self.assertEqual( + "NOT_INSTANTIATED", + vnfr["_admin"]["nsState"], + "Database record must contain 'nsState' NOT_INSTANTIATED" + ) + self.assertEqual( + self.vnfd_id, + vnfr["vnfd-ref"], + "vnfr record is not properly created for the given vnfd") + + def test_show_vnfinstance(self): + session = { + "force": False, + "admin": False, + "public": False, + "project_id": [self.vnfd_project], + "method": "write", + } + filter_q = {} + self.db.create_list("vnfrs", yaml.load(db_vnfrs_text, Loader=yaml.Loader)) + actual_vnfr = self.db.get_list("vnfrs")[0] + id = actual_vnfr["_id"] + expected_vnfr = self.vnfinstances.show(session, id, filter_q) + self.assertEqual( + actual_vnfr["_id"], + expected_vnfr["_id"], + "Mismatch between return vnfr Id and database vnfr Id" + ) + + def test_delete_vnfinstance(self): + session = { + "force": False, + "admin": False, + "public": False, + "project_id": [self.vnfd_project], + "method": "delete", + } + self.db.create_list("vnfrs", yaml.load(db_vnfrs_text, Loader=yaml.Loader)) + self.db.create_list("nsrs", yaml.load(db_nsrs_text, Loader=yaml.Loader)) + self.db.create_list("nsds", yaml.load(db_nsds_text, Loader=yaml.Loader)) + + self.vnfr = self.db.get_list("vnfrs")[0] + self.vnfr_id = self.vnfr["_id"] + self.db.set_one = self.db.set_one + self.db.set_one = Mock() + + self.vnfinstances.delete(session, self.vnfr_id) + msg_args = self.msg.write.call_args[0] + self.assertEqual(msg_args[1], "deleted", "Wrong message action") + + +class TestVnfLcmOpTopic(unittest.TestCase): + def setUp(self): + self.db = DbMemory() + self.fs = Mock(FsBase()) + self.fs.get_params.return_value = {"./fake/folder"} + self.fs.file_open = mock_open() + self.msg = Mock(MsgBase()) + + self.vnflcmop_topic = VnfLcmOpTopic(self.db, self.fs, self.msg, None) + self.vnflcmop_topic.check_quota = Mock(return_value=None) # skip quota + + self.db.create_list( + "vim_accounts", yaml.load(db_vim_accounts_text, Loader=yaml.Loader) + ) + self.db.create_list("nsds", yaml.load(db_nsds_text, Loader=yaml.Loader)) + self.db.create_list("vnfds", yaml.load(db_vnfm_vnfd_text, Loader=yaml.Loader)) + self.db.create_list("vnfrs", yaml.load(db_vnfrs_text, Loader=yaml.Loader)) + self.db.create_list("nsrs", yaml.load(db_nsrs_text, Loader=yaml.Loader)) + + self.vnfd = self.db.get_list("vnfds")[0] + self.vnfd_id = self.vnfd["_id"] + self.vnfr = self.db.get_list("vnfrs")[0] + self.vnfr_id = self.vnfr["_id"] + + self.vnfd_project = self.vnfd["_admin"]["projects_read"][0] + + self.vim = self.db.get_list("vim_accounts")[0] + self.vim_id = self.vim["_id"] + + def test_create_vnf_instantiate(self): + session = { + "force": False, + "admin": False, + "public": False, + "project_id": [self.vnfd_project], + "method": "write", + } + indata = { + "vnfInstanceId": self.vnfr_id, + "lcmOperationType": "instantiate", + "vnfName": "vnf_instance_name", + "vnfDescription": "vnf instance description", + "vnfId": self.vnfd_id, + "vimAccountId": self.vim_id + } + rollback = [] + headers = {} + vnflcmop_id, _ = self.vnflcmop_topic.new( + rollback, session, indata, kwargs=None, headers=headers + ) + vnflcmop_info = self.db.get_one("nslcmops") + self.assertEqual( + vnflcmop_id, + vnflcmop_info["_id"], + "Mismatch between return id and database '_id'", + ) + self.assertTrue( + vnflcmop_info["lcmOperationType"] == "instantiate", + "Database record must contain 'lcmOperationType=instantiate'", + ) + + def test_show_vnflmcop(self): + session = { + "force": False, + "admin": False, + "public": False, + "project_id": [self.vnfd_project], + "method": "write", + } + self.db.create_list("nslcmops", yaml.load(db_nslcmops_text, Loader=yaml.Loader)) + filter_q = {} + actual_lcmop = self.db.get_list("nslcmops")[0] + id = actual_lcmop["_id"] + vnfr = self.db.get_list("vnfrs")[0] + vnfr_id = vnfr["_id"] + vnflcmop = self.vnflcmop_topic.show(session, id, filter_q) + _id = vnflcmop["vnfInstanceId"] + self.assertEqual(_id, vnfr_id, "Mismatch between vnflcmop's vnfInstanceId and database vnfr's id") diff --git a/osm_nbi/vnf_instance_topics.py b/osm_nbi/vnf_instance_topics.py new file mode 100644 index 0000000..ac5fe41 --- /dev/null +++ b/osm_nbi/vnf_instance_topics.py @@ -0,0 +1,116 @@ +# Copyright 2021 K Sai Kiran (Tata Elxsi) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = "K Sai Kiran , Selvi Jayaraman " +__date__ = "$12-June-2021 8:30:59$" + +from osm_nbi.base_topic import BaseTopic +from .osm_vnfm.vnf_instances import VnfInstances2NsInstances +from .osm_vnfm.vnf_instance_actions import VnfLcmOp2NsLcmOp + + +class VnfInstances(BaseTopic): + + def __init__(self, db, fs, msg, auth): + """ + Constructor call for vnf instance topic + """ + BaseTopic.__init__(self, db, fs, msg, auth) + self.vnfinstances2nsinstances = VnfInstances2NsInstances(db, fs, msg, auth) + + def new(self, rollback, session, indata=None, kwargs=None, headers=None): + """ + Creates new vnf instance + :param rollback: list to append the created items at database in case a rollback must be done + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param indata: params to be used for the vnf instance + :param kwargs: used to override the indata descriptor + :param headers: http request headers + :return: the _id of vnf instance created at database. Or an exception. + """ + return self.vnfinstances2nsinstances.new(rollback, session, indata, kwargs, headers) + + def list(self, session, filter_q=None, api_req=False): + """ + Get a list of the vnf instances that match a filter + :param session: contains the used login username and working project + :param filter_q: filter of data to be applied + :param api_req: True if this call is serving an external API request. False if serving internal request. + :return: The list, it can be empty if no one match the filter. + """ + return self.vnfinstances2nsinstances.list(session, filter_q, api_req) + + def show(self, session, _id, filter_q=None, api_req=False): + """ + Get complete information on an vnf instance + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param _id: server internal id + :param api_req: True if this call is serving an external API request. False if serving internal request. + :return: dictionary, raise exception if not found. + """ + return self.vnfinstances2nsinstances.show(session, _id, api_req) + + def delete(self, session, _id, dry_run=False, not_send_msg=None): + """ + Delete vnf instance by its internal _id + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param _id: server internal id + :param dry_run: make checking but do not delete + :param not_send_msg: To not send message (False) or store content (list) instead + :return: operation id (None if there is not operation), raise exception if error or not found, conflict, ... + """ + return self.vnfinstances2nsinstances.delete(session, _id, dry_run, not_send_msg) + + +class VnfLcmOpTopic(BaseTopic): + + def __init__(self, db, fs, msg, auth): + """ + Constructor call for vnf lcm op topic + """ + BaseTopic.__init__(self, db, fs, msg, auth) + self.vnflcmop2nslcmop = VnfLcmOp2NsLcmOp(db, fs, msg, auth) + + def new(self, rollback, session, indata=None, kwargs=None, headers=None): + """ + Creates new vnf lcm op + :param rollback: list to append the created items at database in case a rollback must be done + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param indata: params to be used for the vnf instance + :param kwargs: used to override the indata descriptor + :param headers: http request headers + :return: the _id of vnf lcm op created at database. Or an exception. + """ + return self.vnflcmop2nslcmop.new(rollback, session, indata, kwargs, headers) + + def list(self, session, filter_q=None, api_req=False): + """ + Get a list of the vnf lcm op that match a filter + :param session: contains the used login username and working project + :param filter_q: filter of data to be applied + :param api_req: True if this call is serving an external API request. False if serving internal request. + :return: The list, it can be empty if no one match the filter. + """ + return self.vnflcmop2nslcmop.list(session, filter_q, api_req) + + def show(self, session, _id, filter_q=None, api_req=False): + """ + Get complete information on an vnf lcm op + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param _id: server internal id + :param api_req: True if this call is serving an external API request. False if serving internal request. + :return: dictionary, raise exception if not found. + """ + return self.vnflcmop2nslcmop.show(session, _id, api_req) -- 2.17.1