[DCAEGEN2] PMSH AAI changes with new subscription format

Issue-ID: DCAEGEN2-2912
Change-Id: Ibd21f8f7a01a31bd7db19a39af010d6805b41622
Signed-off-by: SagarS <sagar.shetty@est.tech>
diff --git a/components/pm-subscription-handler/Changelog.md b/components/pm-subscription-handler/Changelog.md
index b346873..77cd827 100755
--- a/components/pm-subscription-handler/Changelog.md
+++ b/components/pm-subscription-handler/Changelog.md
@@ -13,8 +13,9 @@
 * 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 a00c164..fe4166e 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 @@
     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)
 
 
-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.')
+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}')
+
+    @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 7346782..fc27f99 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 @@
     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 29f9121..5fbb9a6 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 @@
     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 @@
             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 fe151c0..1af01cf 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 @@
         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 33e2e91..0005be7 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.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 1975898..42639d9 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 0ae1942..d06b772 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 @@
     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
-        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)
+        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
+        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 fe33832..1843eb4 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 @@
         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 @@
         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 6f0cb63..38acbbb 100644
--- a/components/pm-subscription-handler/tox.ini
+++ b/components/pm-subscription-handler/tox.ini
@@ -31,7 +31,7 @@
 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]