diff options
-rw-r--r-- | src/onapsdk/aai/business/pnf.py | 67 | ||||
-rw-r--r-- | src/onapsdk/aai/templates/aai_put_pnf.json.j2 | 54 | ||||
-rw-r--r-- | src/onapsdk/dmaap/dmaap.py | 19 | ||||
-rw-r--r-- | src/onapsdk/version.py | 2 | ||||
-rw-r--r-- | tests/test_aai_pnf.py | 78 | ||||
-rw-r--r-- | tests/test_dmaap.py | 24 | ||||
-rw-r--r-- | tests/test_version.py | 2 |
7 files changed, 231 insertions, 15 deletions
diff --git a/src/onapsdk/aai/business/pnf.py b/src/onapsdk/aai/business/pnf.py index d8ce746..093b6e1 100644 --- a/src/onapsdk/aai/business/pnf.py +++ b/src/onapsdk/aai/business/pnf.py @@ -13,19 +13,25 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Iterator, Optional, TYPE_CHECKING +import json +from pathlib import Path +from typing import TYPE_CHECKING, Iterator, Optional + +from jinja2 import Environment, FileSystemLoader from onapsdk.exceptions import ResourceNotFound from onapsdk.so.deletion import PnfDeletionRequest + from .instance import Instance if TYPE_CHECKING: from .service import ServiceInstance # pylint: disable=cyclic-import + class PnfInstance(Instance): # pylint: disable=too-many-instance-attributes """Pnf instance class.""" - def __init__(self, # NOSONAR # pylint: disable=too-many-arguments, too-many-locals + def __init__(self, # NOSONAR # pylint: disable=too-many-arguments, too-many-locals service_instance: "ServiceInstance", pnf_name: str, in_maint: bool, @@ -171,10 +177,10 @@ class PnfInstance(Instance): # pylint: disable=too-many-instance-attributes """ for pnf_data in cls.send_message_json( \ - "GET", \ - "Get all pnf instances", \ - cls.get_all_url() \ - ).get("pnf", []): + "GET", \ + "Get all pnf instances", \ + cls.get_all_url() \ + ).get("pnf", []): yield cls.create_from_api_response(pnf_data, None) @property @@ -269,3 +275,52 @@ class PnfInstance(Instance): # pylint: disable=too-many-instance-attributes """ self._logger.debug("Delete %s pnf", self.pnf_name) return PnfDeletionRequest.send_request(self, a_la_carte) + + def delete_from_aai(self): + """DELETE PNF from AAI. + + Send request to AAI to delete PNF from inventory + + """ + self._logger.debug("DELETE %s pnf from AAI", self.pnf_name) + + # Get resource_version from AAI which is needed in DELETE + pnf_get_response = self.send_message_json("GET", + f"Get {self.pnf_name} PNF", + f"{self.url}") + temp_pnf = PnfInstance.create_from_api_response(pnf_get_response, None) + self._logger.info("resource_verison of Pnf is %s", temp_pnf.resource_version) + + delete_url = self.url + "?resource-version=" + temp_pnf.resource_version + delete_response = self.send_message("DELETE", + f"Delete {self.pnf_name} PNF", + f"{delete_url}") + + self._logger.debug("AAI Delete response status code is %s", delete_response.status_code) + + def put_in_aai(self): + """PUT PNF in AAI. + + Send request to AAI to put PNF in inventory + + """ + self._logger.debug("PUT %s pnf in AAI", self.pnf_name) + + environment = Environment(autoescape=True, + loader=FileSystemLoader( + (Path(__file__).parent.parent).joinpath("templates/"))) + template = environment.get_template("aai_put_pnf.json.j2") + environment.globals.update(convert_bool_for_json=json.dumps) + pnf_str = template.render(pnf_object=self) + + # Remove blank lines and comma at the end, if present + req_body = "\n".join(item for item in pnf_str.split('\n') if item).replace(",\n}", "\n}") + + self._logger.debug("PUT request body is %s", req_body) + + put_response = self.send_message("PUT", + f"Put {self.pnf_name} PNF", + f"{self.url}", + data=req_body) + + self._logger.debug("AAI Put response status code is %s", put_response.status_code) diff --git a/src/onapsdk/aai/templates/aai_put_pnf.json.j2 b/src/onapsdk/aai/templates/aai_put_pnf.json.j2 new file mode 100644 index 0000000..65513c7 --- /dev/null +++ b/src/onapsdk/aai/templates/aai_put_pnf.json.j2 @@ -0,0 +1,54 @@ +{ +{% if pnf_object.admin_status %}"admin_status": "{{ pnf_object.admin_status }}",{% else %}{%endif %} +{% if pnf_object.equip_model %}"equip_model": "{{ pnf_object.equip_model }}",{% else %}{%endif %} +{% if pnf_object.equip_type %}"equip_type": "{{ pnf_object.equip_type }}",{% else %}{%endif %} +{% if pnf_object.equip_vendor %}"equip_vendor": "{{ pnf_object.equip_vendor }}",{% else %}{%endif %} +{% if pnf_object.frame_id %}"frame_id": "{{ pnf_object.frame_id }}",{% else %}{%endif %} +{% if pnf_object.in_maint != None %}"in_maint": {{ convert_bool_for_json(pnf_object.in_maint) }},{% else %}{%endif %} +{% if pnf_object.inv_status %}"inv_status": "{{ pnf_object.inv_status }}",{% else %}{%endif %} +{% if pnf_object.ipaddress_v4_aim %}"ipaddress_v4_aim": "{{ pnf_object.ipaddress_v4_aim }}",{% else %}{%endif %} +{% if pnf_object.ipaddress_v4_loopback_0 %}"ipaddress_v4_loopback_0": "{{ pnf_object.ipaddress_v4_loopback_0 }}",{% else %}{%endif %} +{% if pnf_object.ipaddress_v4_oam %}"ipaddress_v4_oam": "{{ pnf_object.ipaddress_v4_oam }}",{% else %}{%endif %} +{% if pnf_object.ipaddress_v6_aim %}"ipaddress_v6_aim": "{{ pnf_object.ipaddress_v6_aim }}",{% else %}{%endif %} +{% if pnf_object.ipaddress_v6_loopback_0 %}"ipaddress_v6_loopback_0": "{{ pnf_object.ipaddress_v6_loopback_0 }}",{% else %}{%endif %} +{% if pnf_object.ipaddress_v6_oam %}"ipaddress_v6_oam": "{{ pnf_object.ipaddress_v6_oam }}",{% else %}{%endif %} +{% if pnf_object.management_option %}"management_option": "{{ pnf_object.management_option }}",{% else %}{%endif %} +{% if pnf_object.model_customization_id %}"model_customization_id": "{{ pnf_object.model_customization_id }}",{% else %}{%endif %} +{% if pnf_object.model_invariant_id %}"model_invariant_id": "{{ pnf_object.model_invariant_id }}",{% else %}{%endif %} +{% if pnf_object.model_version_id %}"model_version_id": "{{ pnf_object.model_version_id }}",{% else %}{%endif %} +{% if pnf_object.nf_role %}"nf_role": "{{ pnf_object.nf_role }}",{% else %}{%endif %} +{% if pnf_object.operational_status %}"operational_status": "{{ pnf_object.operational_status }}",{% else %}{%endif %} +{% if pnf_object.orchestration_status %}"orchestration_status": "{{ pnf_object.orchestration_status }}",{% else %}{%endif %} +{% if pnf_object.pnf_id %}"pnf_id": "{{ pnf_object.pnf_id }}",{% else %}{%endif %} +{% if pnf_object.pnf_ipv4_address %}"pnf_ipv4_address": "{{ pnf_object.pnf_ipv4_address }}",{% else %}{%endif %} +{% if pnf_object.pnf_ipv6_address %}"pnf_ipv6_address": "{{ pnf_object.pnf_ipv6_address }}",{% else %}{%endif %} +{% if pnf_object.pnf_name %}"pnf_name": "{{ pnf_object.pnf_name }}",{% else %}{%endif %} +{% if pnf_object.prov_status %}"prov_status": "{{ pnf_object.prov_status }}",{% else %}{%endif %} +{% if pnf_object.selflink %}"selflink": "{{ pnf_object.selflink }}",{% else %}{%endif %} +{% if pnf_object.serial_number %}"serial_number": "{{ pnf_object.serial_number }}",{% else %}{%endif %} +{% if pnf_object.sw_version %}"sw_version": "{{ pnf_object.sw_version }}",{% else %}{%endif %} +{% if pnf_object.service_instance %}"service_instance": { +{% if pnf_object.service_instance.service_subscription %}"service_subscription": "{{ pnf_object.service_instance.service_subscription }}",{% else %}{%endif %} +{% if pnf_object.service_instance.instance_id %}"instance_id": "{{ pnf_object.service_instance.instance_id }}",{% else %}{%endif %} +{% if pnf_object.service_instance.instance_name %}"instance_name": "{{ pnf_object.service_instance.instance_name }}",{% else %}{%endif %} +{% if pnf_object.service_instance.service_type %}"service_type": "{{ pnf_object.service_instance.service_type }}",{% else %}{%endif %} +{% if pnf_object.service_instance.service_role %}"service_role": "{{ pnf_object.service_instance.service_role }}",{% else %}{%endif %} +{% if pnf_object.service_instance.environment_context %}"environment_context": "{{ pnf_object.service_instance.environment_context }}",{% else %}{%endif %} +{% if pnf_object.service_instance.workload_context %}"workload_context": "{{ pnf_object.service_instance.workload_context }}",{% else %}{%endif %} +{% if pnf_object.service_instance.created_at %}"created_at": "{{ pnf_object.service_instance.created_at }}",{% else %}{%endif %} +{% if pnf_object.service_instance.updated_at %}"updated_at": "{{ pnf_object.service_instance.updated_at }}",{% else %}{%endif %} +{% if pnf_object.service_instance.resource_version %}"resource_version": "{{ pnf_object.service_instance.resource_version }}",{% else %}{%endif %} +{% if pnf_object.service_instance.description %}"description": "{{ pnf_object.service_instance.description }}",{% else %}{%endif %} +{% if pnf_object.service_instance.model_invariant_id %}"model_invariant_id": "{{ pnf_object.service_instance.model_invariant_id }}",{% else %}{%endif %} +{% if pnf_object.service_instance.model_version_id %}"model_version_id": "{{ pnf_object.service_instance.model_version_id }}",{% else %}{%endif %} +{% if pnf_object.service_instance.persona_model_version %}"persona_model_version": "{{ pnf_object.service_instance.persona_model_version }}",{% else %}{%endif %} +{% if pnf_object.service_instance.widget_model_id %}"widget_model_id": "{{ pnf_object.service_instance.widget_model_id }}",{% else %}{%endif %} +{% if pnf_object.service_instance.widget_model_version %}"widget_model_version": "{{ pnf_object.service_instance.widget_model_version }}",{% else %}{%endif %} +{% if pnf_object.service_instance.bandwith_total %}"bandwith_total": "{{ pnf_object.service_instance.bandwith_total }}",{% else %}{%endif %} +{% if pnf_object.service_instance.vhn_portal_url %}"vhn_portal_url": "{{ pnf_object.service_instance.vhn_portal_url }}",{% else %}{%endif %} +{% if pnf_object.service_instance.service_instance_location_id %}"service_instance_location_id": "{{ pnf_object.service_instance.service_instance_location_id }}",{% else %}{%endif %} +{% if pnf_object.service_instance.selflink %}"selflink": "{{ pnf_object.service_instance.selflink }}",{% else %}{%endif %} +{% if pnf_object.service_instance.orchestration_status %}"orchestration_status": "{{ pnf_object.service_instance.orchestration_status }}",{% else %}{%endif %} +{% if pnf_object.service_instance.input_parameters %}"input_parameters": "{{ pnf_object.service_instance.input_parameters }}",{% else %}{%endif %} +}{% else %}{%endif %} +} diff --git a/src/onapsdk/dmaap/dmaap.py b/src/onapsdk/dmaap/dmaap.py index 27c72c5..888ef98 100644 --- a/src/onapsdk/dmaap/dmaap.py +++ b/src/onapsdk/dmaap/dmaap.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. from typing import Dict - from onapsdk.dmaap.dmaap_service import DmaapService ACTION = "Get events from Dmaap" @@ -85,3 +84,21 @@ class Dmaap(DmaapService): url, basic_auth=basic_auth ) + + @classmethod + def post_event(cls, + topic: str, + event: str): + """Post an event on given topic. + + Post an event on given topic by calling DMaaP REST API + + Args: + topic: (str) topic on which to publish the event + event: (str) event payload + + """ + cls.send_message("POST", + f"Publish Event via DMaaP on {topic} topic", + f"{cls.get_all_events_url}/{topic}", + data=event) diff --git a/src/onapsdk/version.py b/src/onapsdk/version.py index 3035ac5..699720e 100644 --- a/src/onapsdk/version.py +++ b/src/onapsdk/version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "12.6.1" +__version__ = "12.7.0" diff --git a/tests/test_aai_pnf.py b/tests/test_aai_pnf.py index 9c417ba..adb5a56 100644 --- a/tests/test_aai_pnf.py +++ b/tests/test_aai_pnf.py @@ -15,11 +15,10 @@ from unittest import mock import pytest -from onapsdk.aai.business import PnfInstance, pnf, ServiceInstance -from onapsdk.exceptions import ResourceNotFound +from onapsdk.aai.business import PnfInstance, ServiceInstance, pnf +from onapsdk.exceptions import ResourceNotFound, APIError, ConnectionFailed from onapsdk.so.deletion import PnfDeletionRequest - PNF_INSTANCE = { "pnf-name": "blablabla", "pnf-id": "546b282b-2ff7-41a4-9329-55c9a2888477", @@ -151,3 +150,76 @@ def test_pnf_instance_pnf(): def test_pnf_count(mock_send_message_json): mock_send_message_json.return_value = COUNT assert PnfInstance.count() == 12 + +@mock.patch.object(PnfInstance,"send_message") +def test_delete_from_aai_success(mock_send_message): + + delete_response = mock.MagicMock() + delete_response.status_code = 204 #success case + + mock_send_message.return_value= delete_response + pnf_instance = PnfInstance(service_instance=None, + pnf_id="test_pnf_id", + pnf_name="test_pnf_name", + serial_number="test_serial_number", + in_maint=False) + try: + pnf_instance.delete_from_aai() + except APIError: + assert False # Exception is not expected + +@mock.patch.object(PnfInstance,"send_message") +def test_delete_from_aai_failure(mock_send_message): + + mock_send_message.side_effect = ConnectionFailed('Can not connect to AAI') + + pnf_instance = PnfInstance(service_instance=None, + pnf_id="test_pnf_id", + pnf_name="test_pnf_name", + serial_number="test_serial_number", + in_maint=False) + with pytest.raises(ConnectionFailed): + pnf_instance.delete_from_aai() + +@mock.patch.object(PnfInstance,"send_message") +def test_put_in_aai_success(mock_send_message): + put_response = mock.MagicMock() + put_response.status_code = 201 #success case + + mock_send_message.return_value = put_response + pnf_instance = PnfInstance(service_instance=None, + pnf_id="test_pnf_id", + pnf_name="test_pnf_name", + serial_number="test_serial_number", + in_maint=False) + try: + pnf_instance.put_in_aai() + except APIError: + assert False # Exception is not expected + +@mock.patch.object(PnfInstance,"send_message") +def test_put_in_aai_success_with_none_attribute(mock_send_message): + put_response = mock.MagicMock() + put_response.status_code = 201 #success case + + mock_send_message.return_value = put_response + pnf_instance = PnfInstance(service_instance=None, + pnf_id="test_pnf_id", + pnf_name="test_pnf_name", + serial_number=None, + in_maint=False) + try: + pnf_instance.put_in_aai() + except APIError: + assert False # Exception is not expected + +@mock.patch.object(PnfInstance,"send_message") +def test_put_in_aai_failure(mock_send_message): + mock_send_message.side_effect = ConnectionFailed('Can not connect to AAI') + pnf_instance = PnfInstance(service_instance=None, + pnf_id="test_pnf_id", + pnf_name="test_pnf_name", + serial_number="test_serial_number", + in_maint=False) + with pytest.raises(ConnectionFailed): + pnf_instance.put_in_aai()
\ No newline at end of file diff --git a/tests/test_dmaap.py b/tests/test_dmaap.py index 28165bc..84ab581 100644 --- a/tests/test_dmaap.py +++ b/tests/test_dmaap.py @@ -11,10 +11,12 @@ # 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. +from unittest import mock from unittest.mock import patch - -from onapsdk.dmaap.dmaap import Dmaap, ACTION, GET_HTTP_METHOD - +from requests import RequestException +from onapsdk.dmaap.dmaap import ACTION, GET_HTTP_METHOD, Dmaap +from onapsdk.exceptions import APIError, ConnectionFailed +import pytest TOPIC = "fault" DMAAP_EVENTS_URL = "http://dmaap.api.simpledemo.onap.org:3904/events" @@ -45,3 +47,19 @@ def verify_send_event_to_ves_called(send_message_mock, dmaap_url): basic_auth=BASIC_AUTH ) +@patch.object(Dmaap, "send_message") +def test_post_event_success(send_message_mock): + post_response = mock.MagicMock() + post_response.status_code = 200 #success case + send_message_mock.return_value = post_response + try: + Dmaap.post_event("test_topic", "test_event") + except RequestException: + assert False # Exception is not expected + + +@patch.object(Dmaap, "send_message") +def test_post_event_failure(send_message_mock): + send_message_mock.side_effect = ConnectionFailed('Can not connect to dmaap') + with pytest.raises(ConnectionFailed): + Dmaap.post_event("test_topic", "test_event") diff --git a/tests/test_version.py b/tests/test_version.py index 264f488..5df9deb 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -17,4 +17,4 @@ import onapsdk.version as version def test_version(): """Check version is the right one.""" - assert version.__version__ == '12.6.1' + assert version.__version__ == '12.7.0' |