[PMSH] Bug fix to include ip in event 83/113883/3 1.1.2-pmsh
authorefiacor <fiachra.corcoran@est.tech>
Wed, 14 Oct 2020 10:17:50 +0000 (11:17 +0100)
committerefiacor <fiachra.corcoran@est.tech>
Mon, 19 Oct 2020 09:03:24 +0000 (10:03 +0100)
   # Add fix for DB cleardown on exit

Signed-off-by: efiacor <fiachra.corcoran@est.tech>
Change-Id: I6630f74258d072f683b5b5e42f0da2e63ea1b3c2
Issue-ID: DCAEGEN2-2486

12 files changed:
components/pm-subscription-handler/Changelog.md
components/pm-subscription-handler/pmsh_service/mod/aai_client.py
components/pm-subscription-handler/pmsh_service/mod/aai_event_handler.py
components/pm-subscription-handler/pmsh_service/mod/api/db_models.py
components/pm-subscription-handler/pmsh_service/mod/exit_handler.py
components/pm-subscription-handler/pmsh_service/mod/network_function.py
components/pm-subscription-handler/pmsh_service/mod/subscription.py
components/pm-subscription-handler/pmsh_service/pmsh_service_main.py
components/pm-subscription-handler/tests/data/pm_subscription_event.json
components/pm-subscription-handler/tests/test_aai_service.py
components/pm-subscription-handler/tests/test_network_function.py
components/pm-subscription-handler/tests/test_subscription.py

index ca98829..6e7e042 100755 (executable)
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 ## [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
index 73bf8d4..48f757d 100755 (executable)
@@ -97,7 +97,7 @@ def _get_aai_service_url():
     """
     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
@@ -135,6 +135,7 @@ def _filter_nf_data(nf_data, app_conf):
             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):
index 61b42b5..f39f1f4 100755 (executable)
@@ -63,6 +63,7 @@ def process_aai_events(mr_sub, mr_pub, app, 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):
index a8b13e8..b6735cc 100755 (executable)
@@ -48,6 +48,7 @@ class SubscriptionModel(db.Model):
     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]}
 
@@ -56,6 +57,7 @@ class NetworkFunctionModel(db.Model):
     __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))
@@ -66,9 +68,10 @@ class NetworkFunctionModel(db.Model):
         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
@@ -82,6 +85,7 @@ class NetworkFunctionModel(db.Model):
         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})
 
@@ -118,7 +122,9 @@ class NfSubRelationalModel(db.Model):
     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,
index 1293296..fbb8b24 100755 (executable)
@@ -16,7 +16,7 @@
 # SPDX-License-Identifier: Apache-2.0
 # ============LICENSE_END=====================================================
 
-from mod import logger
+from mod import logger, db
 from mod.subscription import AdministrativeState
 
 
@@ -39,14 +39,21 @@ class ExitHandler:
     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
index 798da00..3f38388 100755 (executable)
@@ -27,6 +27,7 @@ class NetworkFunction:
     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
@@ -34,11 +35,12 @@ class NetworkFunction:
 
     @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}, ' \
@@ -47,13 +49,14 @@ class NetworkFunction:
     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):
@@ -63,6 +66,7 @@ class NetworkFunction:
 
         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,
@@ -90,7 +94,8 @@ class NetworkFunction:
                             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
@@ -101,8 +106,10 @@ class NetworkFunction:
         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():
@@ -110,7 +117,10 @@ class NetworkFunction:
         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):
@@ -122,6 +132,7 @@ class NetworkFunction:
         if nf:
             db.session.delete(nf)
             db.session.commit()
+        db.session.remove()
 
 
 class NetworkFunctionFilter:
index 21e2399..34753e8 100755 (executable)
@@ -79,6 +79,8 @@ class Subscription:
         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 """
@@ -92,6 +94,8 @@ class Subscription:
         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
@@ -105,7 +109,9 @@ class Subscription:
         """
         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'
@@ -149,8 +155,9 @@ class Subscription:
         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
@@ -160,6 +167,7 @@ class Subscription:
         """
         sub_model = SubscriptionModel.query.filter(
             SubscriptionModel.subscription_name == self.subscriptionName).one_or_none()
+        db.session.remove()
         return sub_model.status
 
     @staticmethod
@@ -169,14 +177,17 @@ class Subscription:
         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)
@@ -209,6 +220,7 @@ class Subscription:
             list(NfSubRelationalModel): NetworkFunctions per Subscription list else empty
         """
         nf_per_subscriptions = NfSubRelationalModel.query.all()
+        db.session.remove()
         return nf_per_subscriptions
 
     @staticmethod
@@ -238,5 +250,5 @@ class Subscription:
             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
index 6b6b9ba..f92fdc9 100755 (executable)
@@ -41,18 +41,22 @@ def main():
             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,
index 9416ec2..cd547d9 100755 (executable)
@@ -5,6 +5,7 @@
    "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
index 7a3b846..2769485 100644 (file)
@@ -72,21 +72,21 @@ class AaiClientTestCase(BaseClassSetup):
     @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)
@@ -98,7 +98,7 @@ class AaiClientTestCase(BaseClassSetup):
     @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):
@@ -111,4 +111,4 @@ class AaiClientTestCase(BaseClassSetup):
             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())
index ea5d2c7..c930c41 100755 (executable)
@@ -33,10 +33,12 @@ class NetworkFunctionTests(BaseClassSetup):
         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:
index 9bfe825..62a9e16 100755 (executable)
@@ -47,6 +47,7 @@ class SubscriptionTest(BaseClassSetup):
         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()
 
@@ -65,12 +66,10 @@ class SubscriptionTest(BaseClassSetup):
 
     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],
@@ -82,16 +81,6 @@ class SubscriptionTest(BaseClassSetup):
         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)
@@ -103,7 +92,6 @@ class SubscriptionTest(BaseClassSetup):
         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()
@@ -152,6 +140,7 @@ class SubscriptionTest(BaseClassSetup):
                                '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'