## [1.1.2]
 ### Changed
 * Bug fix for missing sdnc params in DELETE event (DCAEGEN2-2483)
+* Fix to add IP to event sent to Policy framework (DCAEGEN2-2486)
 
 ## [1.1.1]
 ### Changed
 
     """
     try:
         aai_ssl_port = environ['AAI_SERVICE_PORT']
-        return f'https://aai:{aai_ssl_port}/aai/v20'
+        return f'https://aai:{aai_ssl_port}/aai/v21'
     except KeyError as e:
         logger.error(f'Failed to get AAI env var: {e}', exc_info=True)
         raise
             name_identifier = 'pnf-name' if nf['node-type'] == 'pnf' else 'vnf-name'
             new_nf = mod.network_function.NetworkFunction(
                 nf_name=nf['properties'].get(name_identifier),
+                ip_address=nf['properties'].get('ipaddress-v4-oam'),
                 model_invariant_id=nf['properties'].get('model-invariant-id'),
                 model_version_id=nf['properties'].get('model-version-id'))
             if not new_nf.set_sdnc_params(app_conf):
 
                                 f'is not "Active"')
                     continue
                 nf = NetworkFunction(nf_name=xnf_name,
+                                     ip_address=aai_entity['ipaddress-v4-oam'],
                                      model_invariant_id=aai_entity['model-invariant-id'],
                                      model_version_id=aai_entity['model-version-id'])
                 if not nf.set_sdnc_params(app_conf):
 
     def serialize(self):
         sub_nfs = NfSubRelationalModel.query.filter(
             NfSubRelationalModel.subscription_name == self.subscription_name).all()
+        db.session.remove()
         return {'subscription_name': self.subscription_name, 'subscription_status': self.status,
                 'network_functions': [sub_nf.serialize_nf() for sub_nf in sub_nfs]}
 
     __tablename__ = 'network_functions'
     id = Column(Integer, primary_key=True, autoincrement=True)
     nf_name = Column(String(100), unique=True)
+    ip_address = Column(String(50))
     model_invariant_id = Column(String(100))
     model_version_id = Column(String(100))
     sdnc_model_name = Column(String(100))
         cascade='all, delete-orphan',
         backref='nf')
 
-    def __init__(self, nf_name, model_invariant_id, model_version_id, sdnc_model_name,
+    def __init__(self, nf_name, ip_address, model_invariant_id, model_version_id, sdnc_model_name,
                  sdnc_model_version):
         self.nf_name = nf_name
+        self.ip_address = ip_address
         self.model_invariant_id = model_invariant_id
         self.model_version_id = model_version_id
         self.sdnc_model_name = sdnc_model_name
         return NetworkFunction(sdnc_model_name=self.sdnc_model_name,
                                sdnc_model_version=self.sdnc_model_version,
                                **{'nf_name': self.nf_name,
+                                  'ip_address': self.ip_address,
                                   'model_invariant_id': self.model_invariant_id,
                                   'model_version_id': self.model_version_id})
 
     def serialize_nf(self):
         nf = NetworkFunctionModel.query.filter(
             NetworkFunctionModel.nf_name == self.nf_name).one_or_none()
+        db.session.remove()
         return {'nf_name': self.nf_name,
+                'ip_address': nf.ip_address,
                 'nf_sub_status': self.nf_sub_status,
                 'model_invariant_id': nf.model_invariant_id,
                 'model_version_id': nf.model_version_id,
 
 # SPDX-License-Identifier: Apache-2.0
 # ============LICENSE_END=====================================================
 
-from mod import logger
+from mod import logger, db
 from mod.subscription import AdministrativeState
 
 
     def __call__(self, sig_num, frame):
         logger.info('Graceful shutdown of PMSH initiated.')
         logger.debug(f'ExitHandler was called with signal number: {sig_num}.')
+        for thread in self.periodic_tasks:
+            if thread.name == 'app_conf_thread':
+                logger.info(f'Cancelling thread {thread.name}')
+                thread.cancel()
         current_sub = self.app_conf.subscription
         if current_sub.administrativeState == AdministrativeState.UNLOCKED.value:
             try:
                 current_sub.deactivate_subscription(self.subscription_handler.mr_pub, self.app_conf)
-                current_sub.update_subscription_status()
-                for thread in self.periodic_tasks:
-                    logger.debug(f'Cancelling periodic task with thread name: {thread.name}.')
-                    thread.cancel()
             except Exception as e:
                 logger.error(f'Failed to shut down PMSH application: {e}', exc_info=True)
+        for thread in self.periodic_tasks:
+            logger.info(f'Cancelling thread {thread.name}')
+            thread.cancel()
+        logger.info('Closing all DB connections')
+        db.session.bind.dispose()
+        db.session.close()
+        db.engine.dispose()
         ExitHandler.shutdown_signal_received = True
 
     def __init__(self, sdnc_model_name=None, sdnc_model_version=None, **kwargs):
         """ Object representation of the NetworkFunction. """
         self.nf_name = kwargs.get('nf_name')
+        self.ip_address = kwargs.get('ip_address')
         self.model_invariant_id = kwargs.get('model_invariant_id')
         self.model_version_id = kwargs.get('model_version_id')
         self.sdnc_model_name = sdnc_model_name
 
     @classmethod
     def nf_def(cls):
-        return cls(nf_name=None, model_invariant_id=None, model_version_id=None,
+        return cls(nf_name=None, ip_address=None, model_invariant_id=None, model_version_id=None,
                    sdnc_model_name=None, sdnc_model_version=None)
 
     def __str__(self):
         return f'nf-name: {self.nf_name}, ' \
+               f'ipaddress-v4-oam: {self.ip_address}, ' \
                f'model-invariant-id: {self.model_invariant_id}, ' \
                f'model-version-id: {self.model_version_id}, ' \
                f'sdnc-model-name: {self.sdnc_model_name}, ' \
     def __eq__(self, other):
         return \
             self.nf_name == other.nf_name and \
+            self.ip_address == other.ip_address and \
             self.model_invariant_id == other.model_invariant_id and \
             self.model_version_id == other.model_version_id and \
             self.sdnc_model_name == other.sdnc_model_name and \
             self.sdnc_model_version == other.sdnc_model_version
 
     def __hash__(self):
-        return hash((self.nf_name, self.model_invariant_id,
+        return hash((self.nf_name, self.ip_address, self.model_invariant_id,
                      self.model_version_id, self.sdnc_model_name, self.sdnc_model_version))
 
     def create(self):
 
         if existing_nf is None:
             new_nf = NetworkFunctionModel(nf_name=self.nf_name,
+                                          ip_address=self.ip_address,
                                           model_invariant_id=self.model_invariant_id,
                                           model_version_id=self.model_version_id,
                                           sdnc_model_name=self.sdnc_model_name,
                             f'sdnc-model data associated in AAI: {e}', exc_info=True)
                 return not params_set
         except Exception as e:
-            logger.error(f'Failed to get sdnc-model info for XNFs from AAI: {e}', exc_info=True)
+            logger.error(f'Failed to get sdnc-model info for XNF {self.nf_name} from AAI: {e}',
+                         exc_info=True)
             return not params_set
 
     @staticmethod
         Returns:
             NetworkFunctionModel object else None
         """
-        return NetworkFunctionModel.query.filter(
+        nf_model = NetworkFunctionModel.query.filter(
             NetworkFunctionModel.nf_name == nf_name).one_or_none()
+        db.session.remove()
+        return nf_model
 
     @staticmethod
     def get_all():
         Returns:
             list: NetworkFunctionModel objects else empty
         """
-        return NetworkFunctionModel.query.all()
+
+        nf_models = NetworkFunctionModel.query.all()
+        db.session.remove()
+        return nf_models
 
     @staticmethod
     def delete(**kwargs):
         if nf:
             db.session.delete(nf)
             db.session.commit()
+        db.session.remove()
 
 
 class NetworkFunctionFilter:
 
         except Exception as e:
             logger.error(f'Failed to create subscription {self.subscriptionName} in the DB: {e}',
                          exc_info=True)
+        finally:
+            db.session.remove()
 
     def update_subscription_status(self):
         """ Updates the status of subscription in subscription table """
         except Exception as e:
             logger.error(f'Failed to update status of subscription: {self.subscriptionName}: {e}',
                          exc_info=True)
+        finally:
+            db.session.remove()
 
     def prepare_subscription_event(self, nf, app_conf):
         """Prepare the sub event for publishing
         """
         try:
             clean_sub = {k: v for k, v in self.__dict__.items() if k != 'nfFilter'}
-            sub_event = {'nfName': nf.nf_name, 'blueprintName': nf.sdnc_model_name,
+            sub_event = {'nfName': nf.nf_name,
+                         'ipv4Address': nf.ip_address,
+                         'blueprintName': nf.sdnc_model_name,
                          'blueprintVersion': nf.sdnc_model_version,
                          'policyName': app_conf.operational_policy_name,
                          'changeType': 'DELETE'
         Returns:
             SubscriptionModel object else None
         """
-        return SubscriptionModel.query.filter(
+        sub_model = SubscriptionModel.query.filter(
             SubscriptionModel.subscription_name == self.subscriptionName).one_or_none()
+        return sub_model
 
     def get_local_sub_admin_state(self):
         """ Retrieves the subscription admin state
         """
         sub_model = SubscriptionModel.query.filter(
             SubscriptionModel.subscription_name == self.subscriptionName).one_or_none()
+        db.session.remove()
         return sub_model.status
 
     @staticmethod
         Returns:
             list(SubscriptionModel): Subscriptions list else empty
         """
-        return SubscriptionModel.query.all()
+
+        sub_models = SubscriptionModel.query.all()
+        db.session.remove()
+        return sub_models
 
     def activate_subscription(self, nfs, mr_pub, app_conf):
         logger.info(f'Activate subscription initiated for {self.subscriptionName}.')
         try:
             existing_nfs = self.get_network_functions()
             sub_model = self.get()
-            for nf in set(nfs + existing_nfs):
+            for nf in [new_nf for new_nf in nfs if new_nf not in existing_nfs]:
                 logger.info(f'Publishing event to activate '
                             f'Sub: {self.subscriptionName} for the nf: {nf.nf_name}')
                 mr_pub.publish_subscription_event_data(self, nf, app_conf)
             list(NfSubRelationalModel): NetworkFunctions per Subscription list else empty
         """
         nf_per_subscriptions = NfSubRelationalModel.query.all()
+        db.session.remove()
         return nf_per_subscriptions
 
     @staticmethod
             nf_model_object = NetworkFunctionModel.query.filter(
                 NetworkFunctionModel.nf_name == nf_sub_entry.nf_name).one_or_none()
             nfs.append(nf_model_object.to_nf())
-
+        db.session.remove()
         return nfs
 
             sys.exit(e)
 
         app_conf_thread = PeriodicTask(10, app_conf.refresh_config)
+        app_conf_thread.name = 'app_conf_thread'
         app_conf_thread.start()
 
         policy_response_handler = PolicyResponseHandler(policy_mr_sub, app_conf, app)
         policy_response_handler_thread = PeriodicTask(25, policy_response_handler.poll_policy_topic)
+        policy_response_handler_thread.name = 'policy_event_thread'
 
         aai_event_thread = PeriodicTask(20, process_aai_events,
                                         args=(aai_event_mr_sub, policy_mr_pub, app, app_conf))
+        aai_event_thread.name = 'aai_event_thread'
 
         subscription_handler = SubscriptionHandler(policy_mr_pub, app, app_conf, aai_event_thread,
                                                    policy_response_handler_thread)
 
         subscription_handler_thread = PeriodicTask(30, subscription_handler.execute)
+        subscription_handler_thread.name = 'sub_handler_thread'
         subscription_handler_thread.start()
 
         periodic_tasks = [app_conf_thread, aai_event_thread, subscription_handler_thread,
 
    "policyName":"pmsh-operational-policy",\r
    "changeType":"CREATE",\r
    "closedLoopControlName":"pmsh-control-loop",\r
+   "ipv4Address": "1.2.3.4",\r
    "subscription":{\r
       "subscriptionName":"ExtraPM-All-gNB-R2B",\r
       "administrativeState":"UNLOCKED",\r
 
     @responses.activate
     def test_aai_client_get_all_aai_xnf_data_not_found(self):
         responses.add(responses.PUT,
-                      'https://1.2.3.4:8443/aai/v20/query?format=simple&nodesOnly=true',
+                      'https://1.2.3.4:8443/aai/v21/query?format=simple&nodesOnly=true',
                       json={'error': 'not found'}, status=404)
         self.assertIsNone(aai_client._get_all_aai_nf_data(self.app_conf))
 
     @responses.activate
     def test_aai_client_get_all_aai_xnf_data_success(self):
         responses.add(responses.PUT,
-                      'https://aai:8443/aai/v20/query?format=simple&nodesOnly=true',
+                      'https://aai:8443/aai/v21/query?format=simple&nodesOnly=true',
                       json={'dummy_data': 'blah_blah'}, status=200)
         self.assertIsNotNone(aai_client._get_all_aai_nf_data(self.app_conf))
 
     @responses.activate
     def test_aai_client_get_sdnc_params_success(self):
         responses.add(responses.GET,
-                      'https://aai:8443/aai/v20/service-design-and-creation/models/model/'
+                      'https://aai:8443/aai/v21/service-design-and-creation/models/model/'
                       '6fb9f466-7a79-4109-a2a3-72b340aca53d/model-vers/model-ver/'
                       '6d25b637-8bca-47e2-af1a-61258424183d',
                       json=json.loads(self.good_model_info), status=200)
     @responses.activate
     def test_aai_client_get_sdnc_params_fail(self):
         responses.add(responses.GET,
-                      'https://aai:8443/aai/v20/service-design-and-creation/models/model/'
+                      'https://aai:8443/aai/v21/service-design-and-creation/models/model/'
                       '9fb9f466-7a79-4109-a2a3-72b340aca53d/model-vers/model-ver/'
                       'b7469cc5-be51-41cc-b37f-361537656771', status=404)
         with self.assertRaises(HTTPError):
             aai_client._get_aai_service_url()
 
     def test_aai_client_get_aai_service_url_success(self):
-        self.assertEqual('https://aai:8443/aai/v20', aai_client._get_aai_service_url())
+        self.assertEqual('https://aai:8443/aai/v21', aai_client._get_aai_service_url())
 
         super().setUp()
         self.nf_1 = NetworkFunction(sdnc_model_name='blah', sdnc_model_version=1.0,
                                     **{'nf_name': 'pnf_1',
+                                       'ip_address': '1.2.3.4',
                                        'model_invariant_id': 'some_id',
                                        'model_version_id': 'some_other_id'})
         self.nf_2 = NetworkFunction(sdnc_model_name='blah', sdnc_model_version=2.0,
                                     **{'nf_name': 'pnf_2',
+                                       'ip_address': '1.2.3.4',
                                        'model_invariant_id': 'some_id',
                                        'model_version_id': 'some_other_id'})
         with open(os.path.join(os.path.dirname(__file__), 'data/aai_model_info.json'), 'r') as data:
 
         mock_session_get.return_value.text = self.aai_model_data
         self.mock_mr_sub = Mock()
         self.mock_mr_pub = Mock()
+        self.app_conf.subscription.create()
         self.xnfs = aai_client.get_pmsh_nfs_from_aai(self.app_conf)
         self.sub_model = self.app_conf.subscription.get()
 
 
     def test_get_subscription(self):
         sub_name = 'ExtraPM-All-gNB-R2B'
-        self.app_conf.subscription.create()
         new_sub = self.app_conf.subscription.get()
         self.assertEqual(sub_name, new_sub.subscription_name)
 
     def test_get_nf_names_per_sub(self):
-        self.app_conf.subscription.create()
         self.app_conf.subscription.add_network_function_to_subscription(list(self.xnfs)[0],
                                                                         self.sub_model)
         self.app_conf.subscription.add_network_function_to_subscription(list(self.xnfs)[1],
         self.assertEqual(sub1, same_sub1)
         self.assertEqual(1, len(self.app_conf.subscription.get_all()))
 
-    def test_add_network_functions_per_subscription(self):
-        for nf in self.xnfs:
-            self.app_conf.subscription.add_network_function_to_subscription(nf, self.sub_model)
-        nfs_for_sub_1 = Subscription.get_all_nfs_subscription_relations()
-        self.assertEqual(3, len(nfs_for_sub_1))
-        new_nf = NetworkFunction(nf_name='vnf_3', orchestration_status='Active')
-        self.app_conf.subscription.add_network_function_to_subscription(new_nf, self.sub_model)
-        nf_subs = Subscription.get_all_nfs_subscription_relations()
-        self.assertEqual(4, len(nf_subs))
-
     def test_add_duplicate_network_functions_per_subscription(self):
         self.app_conf.subscription.add_network_function_to_subscription(list(self.xnfs)[0],
                                                                         self.sub_model)
         self.assertEqual(1, len(nf_subs))
 
     def test_update_subscription_status(self):
-        self.app_conf.subscription.create()
         self.app_conf.subscription.administrativeState = 'new_status'
         self.app_conf.subscription.update_subscription_status()
         sub = self.app_conf.subscription.get()
                                'data/pm_subscription_event.json'), 'r') as data:
             expected_sub_event = json.load(data)
         nf = NetworkFunction(nf_name='pnf_1',
+                             ip_address='1.2.3.4',
                              model_invariant_id='some-id',
                              model_version_id='some-id')
         nf.sdnc_model_name = 'some-name'