From bb7533ad0175979f893724eed272148ff65f438c Mon Sep 17 00:00:00 2001 From: "peukerl@telekom.de" Date: Fri, 16 Jun 2023 09:05:05 +0200 Subject: pnf-functionalities added, parameters for service-instantiation added Issue-ID: INT-2250 Signed-off-by: peukerl@telekom.de Change-Id: I71d7ea0e05be0571a195c093a59fe3d08a138493 --- src/onapsdk/aai/business/pnf.py | 18 +++--- src/onapsdk/so/deletion.py | 31 +++++++++++ src/onapsdk/so/instantiation.py | 28 ++++++++++ src/onapsdk/so/templates/deletion_pnf.json.j2 | 27 +++++++++ .../templates/instantiate_pnf_macro_so_pnf.json.j2 | 13 ++++- .../so/templates/instantiate_service_macro.json.j2 | 10 +++- tests/test_aai_pnf.py | 31 ++++++++--- tests/test_service.py | 0 tests/test_so_deletion.py | 19 +++++++ tests/test_so_instantiation.py | 64 ++++++++++++++++++++++ 10 files changed, 223 insertions(+), 18 deletions(-) create mode 100644 src/onapsdk/so/templates/deletion_pnf.json.j2 mode change 100755 => 100644 tests/test_service.py diff --git a/src/onapsdk/aai/business/pnf.py b/src/onapsdk/aai/business/pnf.py index 9061ebf..9f5394f 100644 --- a/src/onapsdk/aai/business/pnf.py +++ b/src/onapsdk/aai/business/pnf.py @@ -16,6 +16,7 @@ from typing import Iterator, Optional, TYPE_CHECKING from onapsdk.exceptions import ResourceNotFound +from onapsdk.so.deletion import PnfDeletionRequest from .instance import Instance if TYPE_CHECKING: @@ -254,14 +255,17 @@ class PnfInstance(Instance): # pylint: disable=too-many-instance-attributes pnf_ipv4_address=api_response.get("pnf-ipv4-address"), pnf_ipv6_address=api_response.get("pnf-ipv6-address")) - def delete(self, a_la_carte: bool = True) -> None: - """Delete Pnf instance. + def delete(self, a_la_carte: bool = True) -> "PnfDeletionRequest": + """Create PNF deletion request. - PNF deletion it's just A&AI resource deletion. That's difference between another instances. - You don't have to wait for that task finish, because it's not async task. + Send request to delete PNF instance + + Args: + a_la_carte (boolean): deletion mode + + Returns: + PnfDeletionRequest: Deletion request """ self._logger.debug("Delete %s pnf", self.pnf_name) - self.send_message("DELETE", - f"Delete {self.pnf_name} PNF", - f"{self.url}?resource-version={self.resource_version}") + return PnfDeletionRequest.send_request(self, a_la_carte) diff --git a/src/onapsdk/so/deletion.py b/src/onapsdk/so/deletion.py index 35ff0ee..5b299f8 100644 --- a/src/onapsdk/so/deletion.py +++ b/src/onapsdk/so/deletion.py @@ -102,6 +102,37 @@ class VnfDeletionRequest(DeletionRequest): # pytest: disable=too-many-ancestors headers=headers_so_creator(OnapService.headers)) return cls(request_id=response["requestReferences"]["requestId"]) +class PnfDeletionRequest(DeletionRequest): # pytest: disable=too-many-ancestors + """PNF deletion class.""" + + @classmethod + def send_request(cls, + instance: "PnfInstance", + a_la_carte: bool = True) -> "PnfDeletionRequest": + """Send request to SO to delete PNF instance. + + Args: + instance (PnfInstance): PNF instance to delete + a_la_carte (boolean): deletion mode + + Returns: + PnfDeletionRequest: Deletion request object + + """ + cls._logger.debug("PNF %s deletion request", instance.pnf_id) + response = cls.send_message_json("DELETE", + f"Create {instance.pnf_id} PNF deletion request", + (f"{cls.base_url}/onap/so/infra/" + f"serviceInstantiation/{cls.api_version}/" + "serviceInstances/" + f"{instance.service_instance.instance_id}/" + f"pnfs/{instance.pnf_id}"), + data=jinja_env(). + get_template("deletion_pnf.json.j2"). + render(pnf_instance=instance, + a_la_carte=a_la_carte), + headers=headers_so_creator(OnapService.headers)) + return cls(request_id=response["requestReferences"]["requestId"]) class ServiceDeletionRequest(DeletionRequest): # pytest: disable=too-many-ancestors """Service deletion request class.""" diff --git a/src/onapsdk/so/instantiation.py b/src/onapsdk/so/instantiation.py index 8130b00..a6781de 100644 --- a/src/onapsdk/so/instantiation.py +++ b/src/onapsdk/so/instantiation.py @@ -97,6 +97,7 @@ class SoServiceVnf(SoServiceXnf): class SoServicePnf(SoServiceXnf): """Class to store a Pnf instance parameters.""" + registration_parameters: Optional["PnfRegistrationParameters"] = None @dataclass class SoService: @@ -109,6 +110,7 @@ class SoService: subscription_service_type: str vnfs: List[SoServiceVnf] = field(default_factory=list) pnfs: List[SoServicePnf] = field(default_factory=list) + parameters: Dict[str, Any] = field(default_factory=dict) instance_name: Optional[str] = None @classmethod @@ -148,6 +150,32 @@ class VfmoduleParameters: vfmodule_parameters: Iterable["InstantiationParameter"] = None +@dataclass +class PnfRegistrationParameters: + """Class to store parameters required for pnf-instantiation without pnf-registration-event. + + Contains required parameters for instantiation request + """ + + model_number: str + oam_v4_ip_address: str + oam_v6_ip_address: str + serial_number: str + software_version: str + unit_type: str + vendor_name: str + + @classmethod + def load(cls, data: Dict[str, Any]) -> "PnfRegistrationParameters": + """Create a PnfRegistrationParameters object from the dict. + + Returns: + PnfRegistrationParameters: PnfRegistrationParameters object created from the dictionary + + """ + return from_dict(data_class=cls, data=data) + + @dataclass class InstantiationParameter: """Class to store instantiation parameters used for preload or macro instantiation. diff --git a/src/onapsdk/so/templates/deletion_pnf.json.j2 b/src/onapsdk/so/templates/deletion_pnf.json.j2 new file mode 100644 index 0000000..852432a --- /dev/null +++ b/src/onapsdk/so/templates/deletion_pnf.json.j2 @@ -0,0 +1,27 @@ +{ + "requestDetails": { + "requestInfo": { + "source": "VID", + "requestorId": "demo" + }, + "modelInfo": { + "modelType": "pnf", + "modelName": "{{ pnf_instance.pnf.model_name }}", + "modelInvariantId": "{{ pnf_instance.pnf.model_invariant_id }}", + "modelVersion": "{{ pnf_instance.pnf.model_version }}", + "modelVersionId": "{{ pnf_instance.pnf.model_version_id }}", + "modelCustomizationId": "{{ pnf_instance.pnf.model_customization_id }}", + "modelCustomizationName": "{{ pnf_instance.pnf.name }}" + }, + "requestParameters": { + "testApi": "GR_API", + "aLaCarte": {{ a_la_carte | tojson }} + }, + {# the code below is needed to be refactored # + } + {# https: //gitlab.com/Orange-OpenSource/lfn/onap/python-onapsdk/-/issues/133 #} "cloudConfiguration": { "cloudOwner": "{{ pnf_instance.service_instance.service_subscription.cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ pnf_instance.service_instance.service_subscription.cloud_region.cloud_region_id }}", + "tenantId": "{{ pnf_instance.service_instance.service_subscription.tenant.tenant_id }}" + } + } +} \ No newline at end of file diff --git a/src/onapsdk/so/templates/instantiate_pnf_macro_so_pnf.json.j2 b/src/onapsdk/so/templates/instantiate_pnf_macro_so_pnf.json.j2 index a6571bd..5f1f22c 100755 --- a/src/onapsdk/so/templates/instantiate_pnf_macro_so_pnf.json.j2 +++ b/src/onapsdk/so/templates/instantiate_pnf_macro_so_pnf.json.j2 @@ -64,6 +64,17 @@ "lineOfBusiness": { "lineOfBusinessName": "{{ line_of_business }}" }, + {% if so_pnf.registration_parameters %} + "pnfRegistrationFields": { + "modelNumber": "{{ so_pnf.registration_parameters.model_number }}", + "oamV4IpAddress": "{{ so_pnf.registration_parameters.oam_v4_ip_address }}", + "oamV6IpAddress": "{{ so_pnf.registration_parameters.oam_v6_ip_address }}", + "serialNumber": "{{ so_pnf.registration_parameters.serial_number }}", + "softwareVersion": "{{ so_pnf.registration_parameters.software_version }}", + "unitType": "{{ so_pnf.registration_parameters.unit_type }}", + "vendorName": "{{ so_pnf.registration_parameters.vendor_name }}" + }, + {% endif %} "productFamilyId": "1234", "instanceName": "{{ instance_name }}", "instanceParams": [ @@ -72,7 +83,7 @@ "{{ key }}": "{{ value }}"{% if not loop.last %},{% endif %} {% endfor %} } - ], + ] } ] {% endblock %} diff --git a/src/onapsdk/so/templates/instantiate_service_macro.json.j2 b/src/onapsdk/so/templates/instantiate_service_macro.json.j2 index 43b92ee..b00a043 100644 --- a/src/onapsdk/so/templates/instantiate_service_macro.json.j2 +++ b/src/onapsdk/so/templates/instantiate_service_macro.json.j2 @@ -44,7 +44,15 @@ {% endif %} { "service": { - "instanceParams": [], + "instanceParams": [ + {% if so_service %} + { + {% for key, value in so_service.parameters.items() %} + "{{ key }}": "{{ value }}"{% if not loop.last %},{% endif %} + {% endfor %} + } + {% endif %} + ], "instanceName": "{{ service_instance_name }}", "resources": { {% block pnfs %} diff --git a/tests/test_aai_pnf.py b/tests/test_aai_pnf.py index eb2e851..9c417ba 100644 --- a/tests/test_aai_pnf.py +++ b/tests/test_aai_pnf.py @@ -15,9 +15,9 @@ from unittest import mock import pytest -from onapsdk.aai.business import PnfInstance, pnf +from onapsdk.aai.business import PnfInstance, pnf, ServiceInstance from onapsdk.exceptions import ResourceNotFound -# from onapsdk.so.deletion import NetworkDeletionRequest +from onapsdk.so.deletion import PnfDeletionRequest PNF_INSTANCE = { @@ -104,13 +104,26 @@ def test_create_pnf_instance_from_api_response(): assert pnf_instance.url.endswith(pnf_instance.pnf_name) -@mock.patch.object(PnfInstance, "send_message") -def test_delete_pnf_instance(mock_send_message): - pnf = PnfInstance(mock.MagicMock, "test_pnf", False) - pnf.delete() - method, _, address = mock_send_message.call_args[0] - assert method == "DELETE" - assert address == f"{pnf.url}?resource-version={pnf.resource_version}" +@mock.patch.object(PnfDeletionRequest, "send_request") +def test_delete_pnf_instance(mock_pnf_deletion_request): + service_instance = ServiceInstance(None, + instance_id="test_service_instance_id") + pnf_instance = PnfInstance(service_instance, + pnf_id="test_pnf_id", + pnf_name="test_pnf_name", + serial_number="test_serial_number", + in_maint=False) + + assert pnf_instance.service_instance == service_instance + assert pnf_instance.pnf_id == "test_pnf_id" + assert pnf_instance.in_maint is False + assert pnf_instance.serial_number == "test_serial_number" + assert pnf_instance._pnf is None + assert pnf_instance.url == (f"{pnf_instance.base_url}{pnf_instance.api_version}/network/" + f"pnfs/pnf/{pnf_instance.pnf_name}") + pnf_instance.delete() + mock_pnf_deletion_request.assert_called_once_with(pnf_instance, True) + def test_pnf_instance_pnf(): diff --git a/tests/test_service.py b/tests/test_service.py old mode 100755 new mode 100644 diff --git a/tests/test_so_deletion.py b/tests/test_so_deletion.py index ff10474..cbcbce3 100644 --- a/tests/test_so_deletion.py +++ b/tests/test_so_deletion.py @@ -14,6 +14,7 @@ from unittest import mock from onapsdk.so.deletion import ( + PnfDeletionRequest, ServiceDeletionRequest, VfModuleDeletionRequest, VnfDeletionRequest @@ -72,3 +73,21 @@ def test_vnf_deletion_request(mock_send_message): f"serviceInstantiation/{VnfDeletionRequest.api_version}/" "serviceInstances/test_service_instance/" "vnfs/test_vnf_id") + +@mock.patch.object(PnfDeletionRequest, "send_message") +def test_pnf_deletion_request(mock_send_message): + mock_pnf_instance = mock.MagicMock() + mock_pnf_instance.pnf_id = "test_pnf_id" + + mock_service_instance = mock.MagicMock() + mock_service_instance.instance_id = "test_service_instance" + mock_pnf_instance.service_instance = mock_service_instance + PnfDeletionRequest.send_request(instance=mock_pnf_instance) + mock_send_message.assert_called_once() + method, _, url = mock_send_message.call_args[0] + assert method == "DELETE" + assert url == (f"{PnfDeletionRequest.base_url}/onap/so/infra/" + f"serviceInstantiation/{PnfDeletionRequest.api_version}/" + "serviceInstances/test_service_instance/" + "pnfs/test_pnf_id") + diff --git a/tests/test_so_instantiation.py b/tests/test_so_instantiation.py index 1374b84..8f2a93e 100644 --- a/tests/test_so_instantiation.py +++ b/tests/test_so_instantiation.py @@ -32,6 +32,7 @@ from onapsdk.so.instantiation import ( VfModuleInstantiation, VnfInstantiation, PnfInstantiation, + PnfRegistrationParameters, ServiceOperation, VnfOperation ) @@ -946,6 +947,59 @@ def test_service_instantiation_multicloud(mock_send_message_json): data = json.loads(kwargs["data"]) assert any(filter(lambda x: x == {"name": "orchestrator", "value": "multicloud"}, data["requestDetails"]["requestParameters"]["userParams"])) +@mock.patch.object(PnfInstantiation, "send_message_json") +@mock.patch.object(OwningEntity, "get_by_owning_entity_id") +def test_pnf_instantiation_so_service(mock_owning_entity_get, mock_send_message_json): + aai_service_instance_mock = mock.MagicMock() + aai_service_instance_mock.instance_id = "test_instance_id" + + relation_1 = mock.MagicMock() + relation_1.related_to = "owning-entity" + relation_1.relationship_data = [{"relationship-value": "test"}] + relation_2 = mock.MagicMock() + relation_2.related_to = "project" + relation_2.relationship_data = [{"relationship-value": "test"}] + + aai_service_instance_mock.relationships = (item for item in [relation_1, relation_2]) + + so_pnf = SoServicePnf( + model_name="test_so_service_pnf_model_name_1", + instance_name="test_so_service_pnf_instance_name_1", + registration_parameters=PnfRegistrationParameters( + model_number="test_model_number", + oam_v4_ip_address="test_ip", + oam_v6_ip_address="test_mac", + serial_number="test_serial_number", + software_version="test_software_version", + unit_type="test_unit_type", + vendor_name="test_vendor" + ) + ) + + _ = PnfInstantiation. \ + instantiate_macro(aai_service_instance=aai_service_instance_mock, + pnf_object=mock.MagicMock(), + line_of_business="test_lob", + platform="test_platform", + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + sdc_service=mock.MagicMock(), + so_pnf=so_pnf) + + _, kwargs = mock_send_message_json.call_args + data = json.loads(kwargs["data"]) + + pnf_data = data["requestDetails"]["requestParameters"]["userParams"][1]["service"]["resources"]["pnfs"][0] + + assert pnf_data["instanceName"] == "test_so_service_pnf_instance_name_1" + + assert pnf_data["pnfRegistrationFields"]["modelNumber"] == "test_model_number" + assert pnf_data["pnfRegistrationFields"]["oamV4IpAddress"] == "test_ip" + assert pnf_data["pnfRegistrationFields"]["oamV6IpAddress"] == "test_mac" + assert pnf_data["pnfRegistrationFields"]["serialNumber"] == "test_serial_number" + assert pnf_data["pnfRegistrationFields"]["softwareVersion"] == "test_software_version" + assert pnf_data["pnfRegistrationFields"]["unitType"] == "test_unit_type" + assert pnf_data["pnfRegistrationFields"]["vendorName"] == "test_vendor" @mock.patch.object(ServiceInstantiation, "send_message_json") def test_service_instantiation_so_service(mock_send_message_json): @@ -954,6 +1008,10 @@ def test_service_instantiation_so_service(mock_send_message_json): so_service = SoService( subscription_service_type="test_so_service", + parameters={ + "service_param_1": "service_param_1_value", + "service_param_2": "service_param_2_value" + }, vnfs=[ SoServiceVnf( model_name="test_so_service_vnf_model_name_1", @@ -1014,11 +1072,17 @@ def test_service_instantiation_so_service(mock_send_message_json): assert data["requestDetails"]["requestParameters"]["subscriptionServiceType"] == "test_so_service" assert len(data["requestDetails"]["requestParameters"]["userParams"][1]["service"]["resources"]["vnfs"]) == 2 assert len(data["requestDetails"]["requestParameters"]["userParams"][1]["service"]["resources"]["pnfs"]) == 2 + + instance_params = data["requestDetails"]["requestParameters"]["userParams"][1]["service"]["instanceParams"] vnf_1_data = data["requestDetails"]["requestParameters"]["userParams"][1]["service"]["resources"]["vnfs"][0] vnf_2_data = data["requestDetails"]["requestParameters"]["userParams"][1]["service"]["resources"]["vnfs"][1] pnf_1_data = data["requestDetails"]["requestParameters"]["userParams"][1]["service"]["resources"]["pnfs"][0] pnf_2_data = data["requestDetails"]["requestParameters"]["userParams"][1]["service"]["resources"]["pnfs"][1] + assert len(instance_params[0]) == 2 + assert instance_params[0]["service_param_1"] == "service_param_1_value" + assert instance_params[0]["service_param_2"] == "service_param_2_value" + assert vnf_1_data["instanceName"] == "test_so_service_vnf_instance_name_1" assert len(vnf_1_data["instanceParams"][0]) == 2 assert vnf_1_data["instanceParams"][0]["param_1"] == "param_1_value" -- cgit 1.2.3-korg