From adcbd374f47858f02a5155ec63d678442fb7f4a0 Mon Sep 17 00:00:00 2001 From: SagarS Date: Thu, 9 Dec 2021 16:59:51 +0000 Subject: [DCAEGEN2] PMSH AAI changes with new subscription format Issue-ID: DCAEGEN2-2912 Change-Id: Ibd21f8f7a01a31bd7db19a39af010d6805b41622 Signed-off-by: SagarS --- components/pm-subscription-handler/Changelog.md | 3 +- .../pmsh_service/mod/aai_event_handler.py | 143 ++++++++++++++------- .../mod/api/services/subscription_service.py | 2 +- .../pmsh_service/mod/subscription_handler.py | 12 -- .../pmsh_service/pmsh_service_main.py | 9 +- .../pm-subscription-handler/tests/base_setup.py | 3 +- .../tests/data/mr_aai_events.json | 3 +- .../tests/test_aai_event_handler.py | 119 ++++++++++++++--- .../tests/test_subscription_handler.py | 3 - components/pm-subscription-handler/tox.ini | 2 +- 10 files changed, 214 insertions(+), 85 deletions(-) (limited to 'components/pm-subscription-handler') diff --git a/components/pm-subscription-handler/Changelog.md b/components/pm-subscription-handler/Changelog.md index b3468736..77cd827a 100755 --- a/components/pm-subscription-handler/Changelog.md +++ b/components/pm-subscription-handler/Changelog.md @@ -13,8 +13,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Added 2 new attributes to the subscription model (DCAEGEN2-2913) * Read subscription API by using subscription name (DCAEGEN2-2818) * Read All subscriptions API (DCAEGEN2-2847) -* PMSH Response Event Handler Integration (DCAEGEN2-2915) +* Response Event Handler Integration (DCAEGEN2-2915) * Updated to get NFs list when requesting a specific subscription (DCAEGEN2-2992) +* AAI Event handler changes with new subscription format (DCAEGEN2-2912) ## [1.3.2] ### Changed diff --git a/components/pm-subscription-handler/pmsh_service/mod/aai_event_handler.py b/components/pm-subscription-handler/pmsh_service/mod/aai_event_handler.py index a00c164a..fe4166e7 100755 --- a/components/pm-subscription-handler/pmsh_service/mod/aai_event_handler.py +++ b/components/pm-subscription-handler/pmsh_service/mod/aai_event_handler.py @@ -15,12 +15,14 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== - import json from enum import Enum - -from mod import logger +from mod import logger, db from mod.network_function import NetworkFunction +from mod.pmsh_config import AppConfig, MRTopic +from mod.api.db_models import SubscriptionModel +from mod.network_function import NetworkFunctionFilter +from mod.api.services import subscription_service class XNFType(Enum): @@ -33,53 +35,98 @@ class AAIEvent(Enum): UPDATE = 'UPDATE' -def process_aai_events(mr_sub, mr_pub, app, app_conf): +def is_pnf_xnf(entity_type): """ - Processes AAI UPDATE events for each filtered xNFs where orchestration status is set to Active. - + Applies measurement groups to network functions identified by AAI event Args: - mr_sub (_MrSub): MR subscriber - mr_pub (_MrPub): MR publisher - app (db): DB application - app_conf (AppConfig): the application configuration. + entity_type (string): The type of network function """ - app.app_context().push() - logger.info('Polling MR for XNF AAI events.') - try: - aai_events = mr_sub.get_from_topic('dcae_pmsh_aai_event') - if aai_events is not None and len(aai_events) != 0: - aai_events = [json.loads(e) for e in aai_events] - xnf_events = [e for e in aai_events if e['event-header']['entity-type'] == ( - XNFType.PNF.value or XNFType.VNF.value)] - for entry in xnf_events: - logger.debug(f'AAI-EVENT entry: {entry}') - aai_entity = entry['entity'] - action = entry['event-header']['action'] - entity_type = entry['event-header']['entity-type'] - xnf_name = aai_entity['pnf-name'] if entity_type == XNFType.PNF.value \ - else aai_entity['vnf-name'] - if aai_entity['orchestration-status'] != 'Active': - logger.info(f'Skipping XNF {xnf_name} as its orchestration-status ' - f'is not "Active"') - continue - nf = NetworkFunction(nf_name=xnf_name, - ipv4_address=aai_entity['ipaddress-v4-oam'], - ipv6_address=aai_entity['ipaddress-v6-oam'], - model_invariant_id=aai_entity['model-invariant-id'], - model_version_id=aai_entity['model-version-id']) - if not nf.set_nf_model_params(app_conf): - continue - if app_conf.nf_filter.is_nf_in_filter(nf): - _process_event(action, nf, mr_pub, app_conf) - except Exception as e: - logger.error(f'Failed to process AAI event: {e}', exc_info=True) + return entity_type == (XNFType.PNF.value or XNFType.VNF.value) + + +class AAIEventHandler: + """ Responsible for handling AAI update events in PMSH """ + + def __init__(self, app): + self.app = app + def execute(self): + """ + Processes AAI UPDATE events for each filtered xNFs where + orchestration status is set to Active. + """ + self.app.app_context().push() + logger.info('Polling MR for XNF AAI events.') + try: + aai_events = AppConfig.get_instance().get_from_topic(MRTopic.AAI_SUBSCRIBER.value, + 'dcae_pmsh_aai_event') + if aai_events is not None and len(aai_events) != 0: + pmsh_nf_names = list(nf.nf_name for nf in NetworkFunction.get_all()) + aai_events = [json.loads(e) for e in aai_events] + xnf_events = [e for e in aai_events if is_pnf_xnf(e['event-header']['entity-type'])] + new_nfs = [] + for entry in xnf_events: + logger.debug(f'AAI-EVENT entry: {entry}') + aai_entity = entry['entity'] + action = entry['event-header']['action'] + entity_type = entry['event-header']['entity-type'] + xnf_name = aai_entity['pnf-name'] if entity_type == XNFType.PNF.value \ + else aai_entity['vnf-name'] + if aai_entity['orchestration-status'] != 'Active': + logger.info(f'Skipping XNF {xnf_name} as its orchestration-status ' + f'is not "Active"') + continue + nf = NetworkFunction(nf_name=xnf_name, + ipv4_address=aai_entity['ipaddress-v4-oam'], + ipv6_address=aai_entity['ipaddress-v6-oam'], + model_invariant_id=aai_entity['model-invariant-id'], + model_version_id=aai_entity['model-version-id']) + if action == AAIEvent.DELETE.value and xnf_name in pmsh_nf_names: + logger.info(f'Delete event found for network function {nf.nf_name}') + NetworkFunction.delete(nf_name=nf.nf_name) + logger.info(f'{nf.nf_name} successfully deleted.') + elif action == AAIEvent.UPDATE.value and \ + xnf_name not in pmsh_nf_names and \ + nf.set_nf_model_params(AppConfig.get_instance()): + new_nfs.append(nf) + if new_nfs: + self.apply_nfs_to_subscriptions(new_nfs) + except Exception as e: + logger.error(f'Failed to process AAI event due to: {e}') -def _process_event(action, nf, mr_pub, app_conf): - if action == AAIEvent.UPDATE.value: - logger.info(f'Update event found for network function {nf.nf_name}') - app_conf.subscription.create_subscription_on_nfs([nf], mr_pub) - elif action == AAIEvent.DELETE.value: - logger.info(f'Delete event found for network function {nf.nf_name}') - NetworkFunction.delete(nf_name=nf.nf_name) - logger.info(f'{nf.nf_name} successfully deleted.') + @staticmethod + def apply_nfs_to_subscriptions(new_nfs): + """ + Applies measurement groups to network functions identified by AAI event + Args: + new_nfs (list[NetworkFunction]): new network functions identified + """ + subscriptions = db.session.query(SubscriptionModel).all() + if subscriptions: + for subscription in subscriptions: + try: + nf_filter = NetworkFunctionFilter(**subscription.network_filter.serialize()) + filtered_nfs = [] + for nf in new_nfs: + if nf_filter.is_nf_in_filter(nf): + filtered_nfs.append(nf) + if filtered_nfs: + subscription_service.save_filtered_nfs(filtered_nfs) + subscription_service. \ + apply_subscription_to_nfs(filtered_nfs, subscription.subscription_name) + unlocked_meas_grp = subscription_service. \ + apply_measurement_grp_to_nfs(filtered_nfs, + subscription.measurement_groups) + if unlocked_meas_grp: + subscription_service. \ + publish_measurement_grp_to_nfs(subscription, filtered_nfs, + unlocked_meas_grp) + else: + logger.error(f'All measurement groups are locked for subscription: ' + f'{subscription.subscription_name}, ' + f'please verify/check measurement groups.') + db.session.commit() + except Exception as e: + logger.error(f'Failed to process AAI event for subscription: ' + f'{subscription.subscription_name} due to: {e}') + db.session.remove() diff --git a/components/pm-subscription-handler/pmsh_service/mod/api/services/subscription_service.py b/components/pm-subscription-handler/pmsh_service/mod/api/services/subscription_service.py index 73467821..fc27f992 100644 --- a/components/pm-subscription-handler/pmsh_service/mod/api/services/subscription_service.py +++ b/components/pm-subscription-handler/pmsh_service/mod/api/services/subscription_service.py @@ -129,7 +129,7 @@ def apply_measurement_grp_to_nfs(filtered_nfs, measurement_groups): Saves measurement groups against nfs with status as PENDING_CREATE Args: - filtered_nfs (list[NetworkFunction])): list of filtered network functions + filtered_nfs (list[NetworkFunction]): list of filtered network functions measurement_groups (list[MeasurementGroupModel]): list of measurement group Returns: diff --git a/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py b/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py index 29f9121d..5fbb9a6c 100644 --- a/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py +++ b/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py @@ -18,9 +18,7 @@ from jsonschema import ValidationError from mod import logger, aai_client -from mod.aai_event_handler import process_aai_events from mod.network_function import NetworkFunctionFilter -from mod.pmsh_utils import PeriodicTask from mod.subscription import AdministrativeState @@ -73,7 +71,6 @@ class SubscriptionHandler: def _activate(self, new_administrative_state): if not self.app_conf.nf_filter: self.app_conf.nf_filter = NetworkFunctionFilter(**self.app_conf.subscription.nfFilter) - self._start_aai_event_thread() self.app_conf.subscription.update_sub_params(new_administrative_state, self.app_conf.subscription.fileBasedGP, self.app_conf.subscription.fileLocation, @@ -91,15 +88,6 @@ class SubscriptionHandler: self.app_conf.subscription.delete_subscription_from_nfs(nfs, self.mr_pub) self.app_conf.subscription.update_subscription_status() - def _start_aai_event_thread(self): - logger.info('Starting polling for NF info on AAI-EVENT topic on DMaaP MR.') - self.aai_event_thread = PeriodicTask(20, process_aai_events, args=(self.aai_sub, - self.mr_pub, - self.app, - self.app_conf)) - self.aai_event_thread.name = 'aai_event_thread' - self.aai_event_thread.start() - def stop_aai_event_thread(self): if self.aai_event_thread is not None: self.aai_event_thread.cancel() diff --git a/components/pm-subscription-handler/pmsh_service/pmsh_service_main.py b/components/pm-subscription-handler/pmsh_service/pmsh_service_main.py index fe151c0d..1af01cf1 100755 --- a/components/pm-subscription-handler/pmsh_service/pmsh_service_main.py +++ b/components/pm-subscription-handler/pmsh_service/pmsh_service_main.py @@ -17,7 +17,7 @@ # ============LICENSE_END===================================================== import sys from signal import signal, SIGTERM - +from mod.aai_event_handler import AAIEventHandler from mod import db, create_app, launch_api_server, logger from mod.exit_handler import ExitHandler from mod.pmsh_config import AppConfig as NewAppConfig @@ -51,8 +51,13 @@ def main(): subscription_handler_thread.name = 'sub_handler_thread' subscription_handler_thread.start() - periodic_tasks = [subscription_handler_thread, policy_response_handler_thread] + aai_event_handler = AAIEventHandler(app) + aai_event_handler_thread = PeriodicTask(20, aai_event_handler.execute) + aai_event_handler_thread.name = 'aai_event_thread' + aai_event_handler_thread.start() + periodic_tasks = [subscription_handler_thread, policy_response_handler_thread, + aai_event_handler_thread] signal(SIGTERM, ExitHandler(periodic_tasks=periodic_tasks, app_conf=app_conf, subscription_handler=subscription_handler)) launch_api_server(pmsh_app_conf) diff --git a/components/pm-subscription-handler/tests/base_setup.py b/components/pm-subscription-handler/tests/base_setup.py index 33e2e91b..0005be74 100755 --- a/components/pm-subscription-handler/tests/base_setup.py +++ b/components/pm-subscription-handler/tests/base_setup.py @@ -22,7 +22,8 @@ from unittest import TestCase from unittest.mock import patch, MagicMock from mod import create_app, db -from mod.api.db_models import NetworkFunctionFilterModel, MeasurementGroupModel, SubscriptionModel, NfSubRelationalModel +from mod.api.db_models import NetworkFunctionFilterModel, MeasurementGroupModel, \ + SubscriptionModel, NfSubRelationalModel from mod.network_function import NetworkFunctionFilter from mod.pmsh_utils import AppConfig from mod.pmsh_config import AppConfig as NewAppConfig diff --git a/components/pm-subscription-handler/tests/data/mr_aai_events.json b/components/pm-subscription-handler/tests/data/mr_aai_events.json index 19758982..42639d9f 100755 --- a/components/pm-subscription-handler/tests/data/mr_aai_events.json +++ b/components/pm-subscription-handler/tests/data/mr_aai_events.json @@ -2,7 +2,8 @@ "mr_response": [ "{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"pnf\",\"top-entity-type\":\"pnf\",\"entity-link\":\"/aai/v16/network/pnfs/pnf/pnf_newly_discovered\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"UPDATE\",\"sequence-number\":\"0\",\"id\":\"db09e090-196e-4f84-9645-e449b1cd3640\",\"source-name\":\"dcae-curl\",\"version\":\"v16\",\"timestamp\":\"20200203-15:14:08:807\"},\"entity\":{\"ipaddress-v4-oam\":\"10.10.10.37\",\"nf-role\":\"gNB\",\"equip-type\":\"val8\",\"relationship-list\":{\"relationship\":[{\"related-to\":\"service-instance\",\"relationship-data\":[{\"relationship-value\":\"Demonstration\",\"relationship-key\":\"customer.global-customer-id\"},{\"relationship-value\":\"vCPE\",\"relationship-key\":\"service-subscription.service-type\"},{\"relationship-value\":\"2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-key\":\"service-instance.service-instance-id\"}],\"related-link\":\"/aai/v16/business/customers/customer/Demonstration/service-subscriptions/service-subscription/vCPE/service-instances/service-instance/2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-label\":\"org.onap.relationships.inventory.ComposedOf\",\"related-to-property\":[{\"property-key\":\"service-instance.service-instance-name\",\"property-value\":\"Svc6_1\"}]}]},\"equip-vendor\":\"Ericsson\",\"serial-number\":\"6061ZW3\",\"ipaddress-v6-oam\":\"2001:0db8:0:0:0:0:1428:57ab\",\"equip-model\":\"val6\",\"in-maint\":false,\"resource-version\":\"1578668956804\",\"sw-version\":\"val7\",\"pnf-id\":\"eabcfaf7-b7f3-45fb-94e7-e6112fb3e8b8\",\"pnf-name\":\"pnf_newly_discovered\",\"model-invariant-id\":\"7129e420-d396-4efb-af02-6b83499b12f8\",\"model-version-id\":\"e80a6ae3-cafd-4d24-850d-e14c084a5ca9\",\"orchestration-status\":\"Active\"}}", "{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"pnf\",\"top-entity-type\":\"pnf\",\"entity-link\":\"/aai/v16/network/pnfs/pnf/pnf_newly_discovered\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"UPDATE\",\"sequence-number\":\"0\",\"id\":\"db09e090-196e-4f84-9645-e449b1cd3640\",\"source-name\":\"dcae-curl\",\"version\":\"v16\",\"timestamp\":\"20200203-15:14:08:807\"},\"entity\":{\"orchestration-status\":\"Active\",\"ipaddress-v4-oam\":\"10.10.10.37\",\"nf-role\":\"gNB\",\"equip-type\":\"val8\",\"relationship-list\":{\"relationship\":[{\"related-to\":\"service-instance\",\"relationship-data\":[{\"relationship-value\":\"Demonstration\",\"relationship-key\":\"customer.global-customer-id\"},{\"relationship-value\":\"vCPE\",\"relationship-key\":\"service-subscription.service-type\"},{\"relationship-value\":\"2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-key\":\"service-instance.service-instance-id\"}],\"related-link\":\"/aai/v16/business/customers/customer/Demonstration/service-subscriptions/service-subscription/vCPE/service-instances/service-instance/2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-label\":\"org.onap.relationships.inventory.ComposedOf\",\"related-to-property\":[{\"property-key\":\"service-instance.service-instance-name\",\"property-value\":\"Svc6_1\"}]}]},\"equip-vendor\":\"Ericsson\",\"serial-number\":\"6061ZW3\",\"ipaddress-v6-oam\":\"2001:0db8:0:0:0:0:1428:57ab\",\"equip-model\":\"val6\",\"in-maint\":false,\"resource-version\":\"1578668956804\",\"sw-version\":\"val7\",\"pnf-id\":\"eabcfaf7-b7f3-45fb-94e7-e6112fb3e8b8\",\"pnf-name\":\"pnf_already_active\",\"model-invariant-id\":\"7129e420-d396-4efb-af02-6b83499b12f8\",\"model-version-id\":\"e80a6ae3-cafd-4d24-850d-e14c084a5ca9\",\"orchestration-status\":\"Active\"}}", - "{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"pnf\",\"top-entity-type\":\"pnf\",\"entity-link\":\"/aai/v16/network/pnfs/pnf/pnf_newly_discovered\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"DELETE\",\"sequence-number\":\"0\",\"id\":\"db09e090-196e-4f84-9645-e449b1cd3640\",\"source-name\":\"dcae-curl\",\"version\":\"v16\",\"timestamp\":\"20200203-15:14:08:807\"},\"entity\":{\"ipaddress-v4-oam\":\"10.10.10.37\",\"nf-role\":\"gNB\",\"equip-type\":\"val8\",\"relationship-list\":{\"relationship\":[{\"related-to\":\"service-instance\",\"relationship-data\":[{\"relationship-value\":\"Demonstration\",\"relationship-key\":\"customer.global-customer-id\"},{\"relationship-value\":\"vCPE\",\"relationship-key\":\"service-subscription.service-type\"},{\"relationship-value\":\"2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-key\":\"service-instance.service-instance-id\"}],\"related-link\":\"/aai/v16/business/customers/customer/Demonstration/service-subscriptions/service-subscription/vCPE/service-instances/service-instance/2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-label\":\"org.onap.relationships.inventory.ComposedOf\",\"related-to-property\":[{\"property-key\":\"service-instance.service-instance-name\",\"property-value\":\"Svc6_1\"}]}]},\"equip-vendor\":\"Ericsson\",\"serial-number\":\"6061ZW3\",\"ipaddress-v6-oam\":\"2001:0db8:0:0:0:0:1428:57ab\",\"equip-model\":\"val6\",\"in-maint\":false,\"resource-version\":\"1578668956804\",\"sw-version\":\"val7\",\"pnf-id\":\"eabcfaf7-b7f3-45fb-94e7-e6112fb3e8b8\",\"pnf-name\":\"pnf_to_be_deleted\",\"model-invariant-id\":\"7129e420-d396-4efb-af02-6b83499b12f8\",\"model-version-id\":\"e80a6ae3-cafd-4d24-850d-e14c084a5ca9\",\"orchestration-status\":\"Active\"}}", + "{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"pnf\",\"top-entity-type\":\"pnf\",\"entity-link\":\"/aai/v16/network/pnfs/pnf/pnf_newly_discovered\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"DELETE\",\"sequence-number\":\"0\",\"id\":\"db09e090-196e-4f84-9645-e449b1cd3640\",\"source-name\":\"dcae-curl\",\"version\":\"v16\",\"timestamp\":\"20200203-15:14:08:807\"},\"entity\":{\"ipaddress-v4-oam\":\"10.10.10.37\",\"nf-role\":\"gNB\",\"equip-type\":\"val8\",\"relationship-list\":{\"relationship\":[{\"related-to\":\"service-instance\",\"relationship-data\":[{\"relationship-value\":\"Demonstration\",\"relationship-key\":\"customer.global-customer-id\"},{\"relationship-value\":\"vCPE\",\"relationship-key\":\"service-subscription.service-type\"},{\"relationship-value\":\"2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-key\":\"service-instance.service-instance-id\"}],\"related-link\":\"/aai/v16/business/customers/customer/Demonstration/service-subscriptions/service-subscription/vCPE/service-instances/service-instance/2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-label\":\"org.onap.relationships.inventory.ComposedOf\",\"related-to-property\":[{\"property-key\":\"service-instance.service-instance-name\",\"property-value\":\"Svc6_1\"}]}]},\"equip-vendor\":\"Ericsson\",\"serial-number\":\"6061ZW3\",\"ipaddress-v6-oam\":\"2001:0db8:0:0:0:0:1428:57ab\",\"equip-model\":\"val6\",\"in-maint\":false,\"resource-version\":\"1578668956804\",\"sw-version\":\"val7\",\"pnf-id\":\"eabcfaf7-b7f3-45fb-94e7-e6112fb3e8b8\",\"pnf-name\":\"pnf_to_be_deleted1\",\"model-invariant-id\":\"7129e420-d396-4efb-af02-6b83499b12f8\",\"model-version-id\":\"e80a6ae3-cafd-4d24-850d-e14c084a5ca9\",\"orchestration-status\":\"Active\"}}", + "{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"pnf\",\"top-entity-type\":\"pnf\",\"entity-link\":\"/aai/v16/network/pnfs/pnf/pnf_newly_discovered\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"DELETE\",\"sequence-number\":\"0\",\"id\":\"db09e090-196e-4f84-9645-e449b1cd3641\",\"source-name\":\"dcae-curl\",\"version\":\"v16\",\"timestamp\":\"20200203-15:14:08:807\"},\"entity\":{\"ipaddress-v4-oam\":\"10.10.10.38\",\"nf-role\":\"gNB\",\"equip-type\":\"val8\",\"relationship-list\":{\"relationship\":[{\"related-to\":\"service-instance\",\"relationship-data\":[{\"relationship-value\":\"Demonstration\",\"relationship-key\":\"customer.global-customer-id\"},{\"relationship-value\":\"vCPE\",\"relationship-key\":\"service-subscription.service-type\"},{\"relationship-value\":\"2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-key\":\"service-instance.service-instance-id\"}],\"related-link\":\"/aai/v16/business/customers/customer/Demonstration/service-subscriptions/service-subscription/vCPE/service-instances/service-instance/2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-label\":\"org.onap.relationships.inventory.ComposedOf\",\"related-to-property\":[{\"property-key\":\"service-instance.service-instance-name\",\"property-value\":\"Svc6_1\"}]}]},\"equip-vendor\":\"Ericsson\",\"serial-number\":\"6061ZW3\",\"ipaddress-v6-oam\":\"2001:0db8:0:0:0:0:1428:57ab\",\"equip-model\":\"val6\",\"in-maint\":false,\"resource-version\":\"1578668956804\",\"sw-version\":\"val7\",\"pnf-id\":\"eabcfaf7-b7f3-45fb-94e7-e6112fb3e8b8\",\"pnf-name\":\"pnf_to_be_deleted2\",\"model-invariant-id\":\"7129e420-d396-4efb-af02-6b83499b12f8\",\"model-version-id\":\"e80a6ae3-cafd-4d24-850d-e14c084a5ca9\",\"orchestration-status\":\"Active\"}}", "{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"model-ver\",\"top-entity-type\":\"model\",\"entity-link\":\"/aai/v19/service-design-and-creation/models/model/60cd7bbf-0a6b-43ce-a5af-07cbf168ecdc/model-vers/model-ver/5485c7ac-825f-414d-aba4-e24147107d0b\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"UPDATE\",\"sequence-number\":\"0\",\"id\":\"e16cb0f9-3617-4a14-9c81-f7ee4cdf9df7\",\"source-name\":\"UNKNOWN\",\"version\":\"v19\",\"timestamp\":\"20200701-12:06:12:135\"},\"entity\":{\"model-type\":\"service\",\"model-vers\":{\"model-ver\":[{\"model-version-id\":\"5485c7ac-825f-414d-aba4-e24147107d0b\",\"resource-version\":\"1593605171956\",\"model-description\":\"catalog service description\",\"model-name\":\"Demo_pNF_WCcM9Z2VNE3eGfLIKulP\",\"model-elements\":{\"model-element\":[{\"relationship-list\":{\"relationship\":[{\"related-to\":\"model-ver\",\"relationship-data\":[{\"relationship-value\":\"82194af1-3c2c-485a-8f44-420e22a9eaa4\",\"relationship-key\":\"model.model-invariant-id\"},{\"relationship-value\":\"46b92144-923a-4d20-b85a-3cbd847668a9\",\"relationship-key\":\"model-ver.model-version-id\"}],\"related-link\":\"/aai/v19/service-design-and-creation/models/model/82194af1-3c2c-485a-8f44-420e22a9eaa4/model-vers/model-ver/46b92144-923a-4d20-b85a-3cbd847668a9\",\"relationship-label\":\"org.onap.relationships.inventory.IsA\",\"related-to-property\":[{\"property-key\":\"model-ver.model-name\",\"property-value\":\"service-instance\"}]}]},\"resource-version\":\"1593605012006\",\"model-elements\":{\"model-element\":[{\"relationship-list\":{\"relationship\":[{\"related-to\":\"model-ver\",\"relationship-data\":[{\"relationship-value\":\"c8fb1064-f38a-4a13-90a6-ec60289879ac\",\"relationship-key\":\"model.model-invariant-id\"},{\"relationship-value\":\"94c051c3-4484-425e-a107-6914816321a6\",\"relationship-key\":\"model-ver.model-version-id\"}],\"related-link\":\"/aai/v19/service-design-and-creation/models/model/c8fb1064-f38a-4a13-90a6-ec60289879ac/model-vers/model-ver/94c051c3-4484-425e-a107-6914816321a6\",\"relationship-label\":\"org.onap.relationships.inventory.IsA\",\"related-to-property\":[{\"property-key\":\"model-ver.model-name\",\"property-value\":\"pNF 77be3d89-b735\"}]}]},\"resource-version\":\"1593605012006\",\"new-data-del-flag\":\"T\",\"cardinality\":\"unbounded\",\"model-element-uuid\":\"44712769-981d-4970-9ea5-4c99b86f838e\"}]},\"new-data-del-flag\":\"T\",\"cardinality\":\"unbounded\",\"model-element-uuid\":\"4a601305-9e09-4152-98ad-b8c466d57813\"}]},\"model-version\":\"1.0\",\"distribution-status\":\"DISTRIBUTION_COMPLETE_OK\"}]},\"model-invariant-id\":\"60cd7bbf-0a6b-43ce-a5af-07cbf168ecdc\"}}", "{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"customer\",\"top-entity-type\":\"customer\",\"entity-link\":\"/aai/v19/business/customers/customer/ETE_Customer_5638d7b4-a02d-4cc4-801e-77ee837d2855\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"CREATE\",\"sequence-number\":\"0\",\"id\":\"ef390383-3d08-4268-a013-d11c14efd716\",\"source-name\":\"robot-ete\",\"version\":\"v19\",\"timestamp\":\"20200701-12:06:50:388\"},\"entity\":{\"global-customer-id\":\"ETE_Customer_5638d7b4-a02d-4cc4-801e-77ee837d2855\",\"subscriber-type\":\"INFRA\",\"resource-version\":\"1593605210258\",\"subscriber-name\":\"ETE_Customer_5638d7b4-a02d-4cc4-801e-77ee837d2855\",\"service-subscriptions\":{\"service-subscription\":[{\"relationship-list\":{\"relationship\":[{\"related-to\":\"tenant\",\"relationship-data\":[{\"relationship-value\":\"CloudOwner\",\"relationship-key\":\"cloud-region.cloud-owner\"},{\"relationship-value\":\"RegionForPNF\",\"relationship-key\":\"cloud-region.cloud-region-id\"},{\"relationship-value\":\"dummy_tenant_id_for_pnf\",\"relationship-key\":\"tenant.tenant-id\"}],\"related-link\":\"/aai/v19/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner/RegionForPNF/tenants/tenant/dummy_tenant_id_for_pnf\",\"relationship-label\":\"org.onap.relationships.inventory.Uses\",\"related-to-property\":[{\"property-key\":\"tenant.tenant-name\",\"property-value\":\"dummy_tenant_for_pnf\"}]}]},\"resource-version\":\"1593605210258\",\"service-type\":\"pNF\"}]}}}" ] diff --git a/components/pm-subscription-handler/tests/test_aai_event_handler.py b/components/pm-subscription-handler/tests/test_aai_event_handler.py index 0ae19429..d06b7728 100755 --- a/components/pm-subscription-handler/tests/test_aai_event_handler.py +++ b/components/pm-subscription-handler/tests/test_aai_event_handler.py @@ -1,5 +1,5 @@ # ============LICENSE_START=================================================== -# Copyright (C) 2020 Nordix Foundation. +# Copyright (C) 2020-2021 Nordix Foundation. # ============================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,27 +15,28 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== +import copy import json from os import path -from unittest.mock import patch, Mock - -from mod.aai_event_handler import process_aai_events +from unittest.mock import patch, MagicMock +from mod.aai_event_handler import AAIEventHandler +from mod.api.db_models import NetworkFunctionModel, NetworkFunctionFilterModel, \ + MeasurementGroupModel, SubscriptionModel +from mod.subscription import AdministrativeState from tests.base_setup import BaseClassSetup +from mod import db class AAIEventHandlerTest(BaseClassSetup): - @classmethod def setUpClass(cls): super().setUpClass() def setUp(self): super().setUp() + super().setUpAppConf() with open(path.join(path.dirname(__file__), 'data/mr_aai_events.json'), 'r') as data: self.mr_aai_events = json.load(data)["mr_response"] - self.mock_mr_sub = Mock(get_from_topic=Mock(return_value=self.mr_aai_events)) - self.mock_mr_pub = Mock() - self.mock_app = Mock() def tearDown(self): super().tearDown() @@ -44,12 +45,100 @@ class AAIEventHandlerTest(BaseClassSetup): def tearDownClass(cls): super().tearDownClass() - @patch('mod.network_function.NetworkFunction.set_nf_model_params') - @patch('mod.subscription.Subscription.create_subscription_on_nfs') + @patch('mod.pmsh_config.AppConfig.get_from_topic') @patch('mod.aai_event_handler.NetworkFunction.delete') - def test_process_aai_update_and_delete_events(self, mock_nf_delete, mock_activate_sub, - mock_set_sdnc_params): + def test_process_aai_delete_events(self, mock_nf_delete, mr_aai_mock): + mr_aai_mock.return_value = self.mr_aai_events + aai_handler = AAIEventHandler(self.app) + network_function = NetworkFunctionModel( + nf_name="pnf_to_be_deleted1", + ipv4_address='204.120.0.15', + ipv6_address='2001:db8:3333:4444:5555:6666:7777:8888', + model_invariant_id='123', + model_version_id='234', + model_name='pnf', + sdnc_model_name='p-node', + sdnc_model_version='v1') + db.session.add(network_function) + network_function2 = copy.deepcopy(network_function) + network_function2.nf_name = "pnf_to_be_deleted2" + db.session.add(network_function2) + db.session.commit() + aai_handler.execute() + self.assertEqual(mock_nf_delete.call_count, 2) + + @patch('mod.aai_event_handler.AAIEventHandler.apply_nfs_to_subscriptions') + @patch('mod.network_function.NetworkFunction.set_nf_model_params') + @patch('mod.pmsh_config.AppConfig.get_from_topic') + def test_process_aai_update_events(self, mr_aai_mock, mock_set_sdnc_params, mock_apply_nfs): + mock_set_sdnc_params.return_value = True + mr_aai_mock.return_value = self.mr_aai_events + aai_handler = AAIEventHandler(self.app) + aai_handler.execute() + self.assertEqual(mock_apply_nfs.call_count, 1) + + @patch('mod.pmsh_config.AppConfig.publish_to_topic', MagicMock(return_value=None)) + @patch('mod.api.services.subscription_service.apply_measurement_grp_to_nfs') + @patch('mod.network_function.NetworkFunction.set_nf_model_params') + @patch('mod.pmsh_config.AppConfig.get_from_topic') + def test_process_aai_apply_nfs_to_subscriptions(self, mr_aai_mock, mock_set_sdnc_params, + apply_nfs_to_measure_grp): mock_set_sdnc_params.return_value = True - process_aai_events(self.mock_mr_sub, self.mock_mr_pub, self.mock_app, self.app_conf) - self.assertEqual(mock_activate_sub.call_count, 2) - mock_nf_delete.assert_called_once() + mr_aai_mock.return_value = self.mr_aai_events + aai_handler = AAIEventHandler(self.app) + subscription = SubscriptionModel(subscription_name='ExtraPM-All-gNB-R2B2', + operational_policy_name='operation_policy', + control_loop_name="control-loop", + status=AdministrativeState.UNLOCKED.value) + db.session.add(subscription) + db.session.commit() + generate_nf_filter_measure_grp('ExtraPM-All-gNB-R2B', 'msr_grp_name') + generate_nf_filter_measure_grp('ExtraPM-All-gNB-R2B2', 'msr_grp_name2') + db.session.commit() + aai_handler.execute() + self.assertEqual(apply_nfs_to_measure_grp.call_count, 2) + + @patch('mod.pmsh_config.AppConfig.publish_to_topic', MagicMock(return_value=None)) + @patch('mod.api.services.subscription_service.apply_measurement_grp_to_nfs') + @patch('mod.network_function.NetworkFunction.set_nf_model_params') + @patch('mod.logger.error') + @patch('mod.pmsh_config.AppConfig.get_from_topic') + def test_process_aai_apply_msg_failure(self, mr_aai_mock, mock_logger, mock_set_sdnc_params, + apply_nfs_to_measure_grp): + mock_set_sdnc_params.return_value = True + mr_aai_mock.return_value = self.mr_aai_events + apply_nfs_to_measure_grp.side_effect = Exception("publish failed") + aai_handler = AAIEventHandler(self.app) + generate_nf_filter_measure_grp('ExtraPM-All-gNB-R2B', 'msr_grp_name3') + db.session.commit() + aai_handler.execute() + mock_logger.assert_called_with('Failed to process AAI event for ' + 'subscription: ExtraPM-All-gNB-R2B ' + 'due to: publish failed') + + @patch('mod.pmsh_config.AppConfig.publish_to_topic', MagicMock(return_value=None)) + @patch('mod.network_function.NetworkFunction.set_nf_model_params') + @patch('mod.logger.error') + @patch('mod.pmsh_config.AppConfig.get_from_topic') + def test_process_aai__failure(self, mr_aai_mock, mock_logger, mock_set_sdnc_params): + mock_set_sdnc_params.return_value = True + mr_aai_mock.side_effect = Exception("AAI failure") + aai_handler = AAIEventHandler(self.app) + generate_nf_filter_measure_grp('ExtraPM-All-gNB-R2B', 'msr_grp_name3') + db.session.commit() + aai_handler.execute() + mock_logger.assert_called_with('Failed to process AAI event due to: AAI failure') + + +def generate_nf_filter_measure_grp(sub_name, msg_name): + nf_filter = NetworkFunctionFilterModel( + subscription_name=sub_name, nf_names='{^pnf.*, ^vnf.*}', + model_invariant_ids='{}', + model_version_ids='{}', + model_names='{}') + measurement_group = MeasurementGroupModel( + subscription_name=sub_name, measurement_group_name=msg_name, + administrative_state='UNLOCKED', file_based_gp=15, file_location='pm.xml', + measurement_type=[], managed_object_dns_basic=[]) + db.session.add(nf_filter) + db.session.add(measurement_group) diff --git a/components/pm-subscription-handler/tests/test_subscription_handler.py b/components/pm-subscription-handler/tests/test_subscription_handler.py index fe338327..1843eb44 100644 --- a/components/pm-subscription-handler/tests/test_subscription_handler.py +++ b/components/pm-subscription-handler/tests/test_subscription_handler.py @@ -61,8 +61,6 @@ class SubscriptionHandlerTest(BaseClassSetup): mock_logger.assert_called_with('Administrative State did not change ' 'in the app config: UNLOCKED') - @patch('mod.subscription_handler.SubscriptionHandler._start_aai_event_thread', - MagicMock()) @patch('mod.pmsh_utils.AppConfig.refresh_config', MagicMock(return_value=get_pmsh_config())) @patch('mod.subscription.Subscription.get_local_sub_admin_state') @patch('mod.subscription.Subscription.create_subscription_on_nfs') @@ -91,7 +89,6 @@ class SubscriptionHandlerTest(BaseClassSetup): sub_handler.execute() mock_deactivate_sub.assert_called_with(self.nfs, self.mock_mr_pub) - @patch('mod.subscription_handler.SubscriptionHandler._start_aai_event_thread', MagicMock()) @patch('mod.pmsh_utils.AppConfig.refresh_config', MagicMock(return_value=get_pmsh_config())) @patch('mod.subscription.Subscription.create_subscription_on_nfs') @patch('mod.logger.error') diff --git a/components/pm-subscription-handler/tox.ini b/components/pm-subscription-handler/tox.ini index 6f0cb630..38acbbb8 100644 --- a/components/pm-subscription-handler/tox.ini +++ b/components/pm-subscription-handler/tox.ini @@ -31,7 +31,7 @@ deps= setenv = PYTHONPATH={toxinidir}/pmsh_service:{toxinidir}/pmsh_service/mod:{toxinidir}/tests commands= - pytest --junitxml xunit-results.xml --cov pmsh_service --cov-report xml --cov-report term \ + pytest --junitxml xunit-results.xml --cov pmsh_service --cov-report xml --cov-report term-missing \ tests --verbose --cov-fail-under=70 [testenv:flake8] -- cgit 1.2.3-korg