diff options
author | Joseph O'Leary <joseph.o.leary@est.tech> | 2020-03-25 10:01:25 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@onap.org> | 2020-03-25 10:01:25 +0000 |
commit | 8bfed3e53e55f711b1eb7d9cca696b3519658842 (patch) | |
tree | 05392eb99c110f48c0fc9d4e14950e566bdee678 /components | |
parent | 66c3554a51d7af0723261f26fa483f261086e6de (diff) | |
parent | b074a929a43629a5d4ced09f1ebe4106241d776f (diff) |
Merge "[PMSH] Refactor subscription processor and policy response handler"
Diffstat (limited to 'components')
20 files changed, 463 insertions, 358 deletions
diff --git a/components/pm-subscription-handler/.coveragerc b/components/pm-subscription-handler/.coveragerc index d1c3205b..23af084a 100644 --- a/components/pm-subscription-handler/.coveragerc +++ b/components/pm-subscription-handler/.coveragerc @@ -21,6 +21,7 @@ branch = True cover_pylib = False include = */pmsh_service/*.py +omit = pmsh_service/pmsh_service_main.py [report] # Regexes for lines to exclude from consideration diff --git a/components/pm-subscription-handler/ChangeLog.md b/components/pm-subscription-handler/ChangeLog.md deleted file mode 100644 index 2d9c89a8..00000000 --- a/components/pm-subscription-handler/ChangeLog.md +++ /dev/null @@ -1,10 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/) -and this project adheres to [Semantic Versioning](http://semver.org/). - -## [1.0.0] - -* Initial release of the PM Subscription Handler. diff --git a/components/pm-subscription-handler/Changelog.md b/components/pm-subscription-handler/Changelog.md index 87c282ff..0a849bab 100644 --- a/components/pm-subscription-handler/Changelog.md +++ b/components/pm-subscription-handler/Changelog.md @@ -1,7 +1,20 @@ # Change Log + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [1.0.0] - 11/4/2019
\ No newline at end of file +## [1.0.2] +### Changed +* Moved subscription processing from main into its own subscription_handler module +* Removed policy response handling functions from pmsh_utils and introduced policy_response_handler +* Network function filter now resides in network_function instead of subscription + +## [1.0.1] +### Fixed +* Fixed Deletion of Network Function + +## [1.0.0] + +* Initial release of the PM Subscription Handler. diff --git a/components/pm-subscription-handler/pmsh_service/mod/aai_client.py b/components/pm-subscription-handler/pmsh_service/mod/aai_client.py index f0f20566..489d035b 100755 --- a/components/pm-subscription-handler/pmsh_service/mod/aai_client.py +++ b/components/pm-subscription-handler/pmsh_service/mod/aai_client.py @@ -23,8 +23,8 @@ import requests from requests.auth import HTTPBasicAuth import mod.pmsh_logging as logger -from mod.network_function import NetworkFunction -from mod.subscription import Subscription, NetworkFunctionFilter +from mod.network_function import NetworkFunction, NetworkFunctionFilter +from mod.subscription import Subscription def get_pmsh_subscription_data(cbs_data): 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 9d69e760..ee75fbf5 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 @@ -20,8 +20,7 @@ import json from enum import Enum from mod import pmsh_logging as logger -from mod.network_function import NetworkFunction -from mod.subscription import NetworkFunctionFilter +from mod.network_function import NetworkFunction, NetworkFunctionFilter class XNFType(Enum): diff --git a/components/pm-subscription-handler/pmsh_service/mod/config_handler.py b/components/pm-subscription-handler/pmsh_service/mod/config_handler.py index acf5b76f..26b03153 100755 --- a/components/pm-subscription-handler/pmsh_service/mod/config_handler.py +++ b/components/pm-subscription-handler/pmsh_service/mod/config_handler.py @@ -15,6 +15,7 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== + from os import environ import requests diff --git a/components/pm-subscription-handler/pmsh_service/mod/network_function.py b/components/pm-subscription-handler/pmsh_service/mod/network_function.py index 2150dc28..1cdf57a0 100755 --- a/components/pm-subscription-handler/pmsh_service/mod/network_function.py +++ b/components/pm-subscription-handler/pmsh_service/mod/network_function.py @@ -15,6 +15,8 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== + +import re from mod import pmsh_logging as logger, db from mod.db_models import NetworkFunctionModel @@ -85,3 +87,21 @@ class NetworkFunction: if nf: db.session.delete(nf) db.session.commit() + + +class NetworkFunctionFilter: + def __init__(self, **kwargs): + self.nf_sw_version = kwargs.get('swVersions') + self.nf_names = kwargs.get('nfNames') + self.regex_matcher = re.compile('|'.join(raw_regex for raw_regex in self.nf_names)) + + def is_nf_in_filter(self, nf_name): + """Match the nf name against regex values in Subscription.nfFilter.nfNames + + Args: + nf_name: the AAI nf name. + + Returns: + bool: True if matched, else False. + """ + return self.regex_matcher.search(nf_name) diff --git a/components/pm-subscription-handler/pmsh_service/mod/pmsh_utils.py b/components/pm-subscription-handler/pmsh_service/mod/pmsh_utils.py index c8b3bc77..1fc3a097 100755 --- a/components/pm-subscription-handler/pmsh_service/mod/pmsh_utils.py +++ b/components/pm-subscription-handler/pmsh_service/mod/pmsh_utils.py @@ -15,18 +15,12 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== -import json -import threading -import uuid -from threading import Timer +import uuid import requests -from requests.auth import HTTPBasicAuth -from tenacity import retry, wait_fixed, retry_if_exception_type - import mod.pmsh_logging as logger -from mod.network_function import NetworkFunction -from mod.subscription import Subscription, SubNfState, AdministrativeState +from requests.auth import HTTPBasicAuth +from threading import Timer class AppConfig: @@ -179,74 +173,6 @@ class _MrSub(_DmaapMrClient): logger.debug(e) return topic_data - @staticmethod - def _handle_response(subscription_name, administrative_state, nf_name, response_message): - """ - Handles the response from Policy, updating the DB - - Args: - subscription_name (str): The subscription name - administrative_state (str): The administrative state of the subscription - nf_name (str): The network function name - response_message (str): The message in the response regarding the state (success|failed) - """ - logger.debug(f'Response from MR: Sub: {subscription_name} for ' - f'NF: {nf_name} received, updating the DB') - try: - sub_nf_status = subscription_nf_states[administrative_state][response_message].value - policy_response_handle_functions[administrative_state][response_message]( - subscription_name=subscription_name, status=sub_nf_status, nf_name=nf_name) - except Exception as err: - raise Exception(f'Error changing nf_sub status in the DB: {err}') - - @retry(wait=wait_fixed(5), retry=retry_if_exception_type(Exception)) - def poll_policy_topic(self, subscription_name, app): - """ - This method polls MR for response from policy. It checks whether the message is for the - relevant subscription and then handles the response - - Args: - subscription_name (str): The subscription name - app (app): Needed to push context for the db - """ - app.app_context().push() - administrative_state = Subscription.get(subscription_name).status - try: - response_data = self.get_from_topic('policy_response_consumer') - for data in response_data: - data = json.loads(data) - if data['status']['subscriptionName'] == subscription_name: - nf_name = data['status']['nfName'] - response_message = data['status']['message'] - self._handle_response(subscription_name, administrative_state, - nf_name, response_message) - threading.Timer(5, self.poll_policy_topic, [subscription_name, app]).start() - except Exception as err: - raise Exception(f'Error trying to poll MR: {err}') - - -subscription_nf_states = { - AdministrativeState.LOCKED.value: { - 'success': SubNfState.CREATED, - 'failed': SubNfState.DELETE_FAILED - }, - AdministrativeState.UNLOCKED.value: { - 'success': SubNfState.CREATED, - 'failed': SubNfState.CREATE_FAILED - } -} - -policy_response_handle_functions = { - AdministrativeState.LOCKED.value: { - 'success': NetworkFunction.delete, - 'failed': Subscription.update_sub_nf_status - }, - AdministrativeState.UNLOCKED.value: { - 'success': Subscription.update_sub_nf_status, - 'failed': Subscription.update_sub_nf_status - } -} - class PeriodicTask(Timer): """ diff --git a/components/pm-subscription-handler/pmsh_service/mod/policy_response_handler.py b/components/pm-subscription-handler/pmsh_service/mod/policy_response_handler.py new file mode 100644 index 00000000..aa5a8cb8 --- /dev/null +++ b/components/pm-subscription-handler/pmsh_service/mod/policy_response_handler.py @@ -0,0 +1,84 @@ +# ============LICENSE_START=================================================== +# Copyright (C) 2020 Nordix Foundation. +# ============================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END===================================================== + +import json + +from tenacity import retry, wait_fixed, retry_if_exception_type + +import mod.pmsh_logging as logger +from mod.network_function import NetworkFunction +from mod.subscription import Subscription, AdministrativeState, subscription_nf_states + + +policy_response_handle_functions = { + AdministrativeState.LOCKED.value: { + 'success': NetworkFunction.delete, + 'failed': Subscription.update_sub_nf_status + }, + AdministrativeState.UNLOCKED.value: { + 'success': Subscription.update_sub_nf_status, + 'failed': Subscription.update_sub_nf_status + } +} + + +class PolicyResponseHandler: + def __init__(self, mr_sub, subscription_name, app): + self.mr_sub = mr_sub + self.subscription_name = subscription_name + self.app = app + + @retry(wait=wait_fixed(5), retry=retry_if_exception_type(Exception)) + def poll_policy_topic(self): + """ + This method polls MR for response from policy. It checks whether the message is for the + relevant subscription and then handles the response + """ + self.app.app_context().push() + administrative_state = Subscription.get(self.subscription_name).status + try: + response_data = self.mr_sub.get_from_topic('policy_response_consumer') + for data in response_data: + data = json.loads(data) + if data['status']['subscriptionName'] == self.subscription_name: + nf_name = data['status']['nfName'] + response_message = data['status']['message'] + self._handle_response(self.subscription_name, administrative_state, + nf_name, response_message) + except Exception as err: + raise Exception(f'Error trying to poll policy response topic on MR: {err}') + + @staticmethod + def _handle_response(subscription_name, administrative_state, nf_name, response_message): + """ + Handles the response from Policy, updating the DB + + Args: + subscription_name (str): The subscription name + administrative_state (str): The administrative state of the subscription + nf_name (str): The network function name + response_message (str): The message in the response regarding the state (success|failed) + """ + logger.debug(f'Response from MR: Sub: {subscription_name} for ' + f'NF: {nf_name} received, updating the DB') + try: + sub_nf_status = subscription_nf_states[administrative_state][response_message].value + policy_response_handle_functions[administrative_state][response_message]( + subscription_name=subscription_name, status=sub_nf_status, nf_name=nf_name) + except Exception as err: + raise Exception(f'Error changing nf_sub status in the DB: {err}') diff --git a/components/pm-subscription-handler/pmsh_service/mod/subscription.py b/components/pm-subscription-handler/pmsh_service/mod/subscription.py index 5449f420..99a787da 100755 --- a/components/pm-subscription-handler/pmsh_service/mod/subscription.py +++ b/components/pm-subscription-handler/pmsh_service/mod/subscription.py @@ -15,13 +15,14 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== -import re + from enum import Enum +from tenacity import retry, retry_if_exception_type, wait_exponential, stop_after_attempt + import mod.pmsh_logging as logger from mod import db from mod.db_models import SubscriptionModel, NfSubRelationalModel -from tenacity import retry, retry_if_exception_type, wait_exponential, stop_after_attempt class SubNfState(Enum): @@ -37,6 +38,18 @@ class AdministrativeState(Enum): LOCKED = 'LOCKED' +subscription_nf_states = { + AdministrativeState.LOCKED.value: { + 'success': SubNfState.CREATED, + 'failed': SubNfState.DELETE_FAILED + }, + AdministrativeState.UNLOCKED.value: { + 'success': SubNfState.CREATED, + 'failed': SubNfState.CREATE_FAILED + } +} + + class Subscription: def __init__(self, **kwargs): self.subscriptionName = kwargs.get('subscriptionName') @@ -205,21 +218,3 @@ class Subscription: update({NfSubRelationalModel.nf_sub_status: status}, synchronize_session='evaluate') db.session.commit() - - -class NetworkFunctionFilter: - def __init__(self, **kwargs): - self.nf_sw_version = kwargs.get('swVersions') - self.nf_names = kwargs.get('nfNames') - self.regex_matcher = re.compile('|'.join(raw_regex for raw_regex in self.nf_names)) - - def is_nf_in_filter(self, nf_name): - """Match the nf name against regex values in Subscription.nfFilter.nfNames - - Args: - nf_name: the AAI nf name. - - Returns: - bool: True if matched, else False. - """ - return self.regex_matcher.search(nf_name) diff --git a/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py b/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py new file mode 100644 index 00000000..a615aa77 --- /dev/null +++ b/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py @@ -0,0 +1,59 @@ +# ============LICENSE_START=================================================== +# Copyright (C) 2020 Nordix Foundation. +# ============================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END===================================================== + +import mod.aai_client as aai +import mod.pmsh_logging as logger +from mod.subscription import AdministrativeState + + +class SubscriptionHandler: + def __init__(self, config_handler, administrative_state, mr_pub, + aai_event_thread, app, app_conf): + self.config_handler = config_handler + self.administrative_state = administrative_state + self.mr_pub = mr_pub + self.aai_event_thread = aai_event_thread + self.app = app + self.app_conf = app_conf + + def execute(self): + """ + Checks for changes of administrative state in config and proceeds to process + the Subscription if a change has occurred + """ + self.app.app_context().push() + config = self.config_handler.get_config() + new_administrative_state = config['policy']['subscription']['administrativeState'] + + try: + if self.administrative_state == new_administrative_state: + logger.debug('Administrative State did not change in the Config') + else: + sub, network_functions = aai.get_pmsh_subscription_data(config) + self.administrative_state = new_administrative_state + sub.process_subscription(network_functions, self.mr_pub, self.app_conf) + + if new_administrative_state == AdministrativeState.UNLOCKED.value: + logger.debug('Listening to AAI-EVENT topic in MR.') + self.aai_event_thread.start() + else: + logger.debug('Stop listening to AAI-EVENT topic in MR.') + self.aai_event_thread.cancel() + + except Exception as err: + logger.debug(f'Error occurred during the activation/deactivation process {err}') 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 8245466b..af5aece2 100755 --- a/components/pm-subscription-handler/pmsh_service/pmsh_service_main.py +++ b/components/pm-subscription-handler/pmsh_service/pmsh_service_main.py @@ -15,61 +15,18 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== + import sys -import threading import mod.aai_client as aai import mod.pmsh_logging as logger from mod import db, create_app, launch_api_server -from mod.aai_event_handler import process_aai_events from mod.config_handler import ConfigHandler from mod.pmsh_utils import AppConfig, PeriodicTask +from mod.policy_response_handler import PolicyResponseHandler from mod.subscription import Subscription, AdministrativeState - - -def subscription_processor(config_handler, administrative_state, mr_pub, app, - mr_aai_event_subscriber): - """ - Checks for changes of administrative state in config and proceeds to process - the Subscription if a change has occurred - - Args: - config_handler (ConfigHandler): Configuration Handler used to get config - administrative_state (str): The administrative state - mr_pub (_MrPub): MR publisher - app (db): DB application - mr_aai_event_subscriber (_MrSub): AAI events MR subscriber - """ - app.app_context().push() - config = config_handler.get_config() - app_conf = AppConfig(**config['config']) - new_administrative_state = config['policy']['subscription']['administrativeState'] - polling_period = 30.0 - - try: - if administrative_state == new_administrative_state: - logger.debug('Administrative State did not change in the Config') - else: - logger.debug(f'Administrative State changed from "{administrative_state}" "to ' - f'"{new_administrative_state}".') - sub, nfs = aai.get_pmsh_subscription_data(config) - sub.process_subscription(nfs, mr_pub, app_conf) - aai_event_thread = PeriodicTask(10, process_aai_events, args=( - mr_aai_event_subscriber, sub, mr_pub, app, app_conf)) - - if new_administrative_state == AdministrativeState.UNLOCKED.value: - logger.debug('Listening to AAI-EVENT topic in MR.') - aai_event_thread.start() - else: - logger.debug('Stopping to listen to AAI-EVENT topic in MR.') - aai_event_thread.cancel() - - except Exception as err: - logger.debug(f'Error occurred during the activation/deactivation process {err}') - - threading.Timer(polling_period, subscription_processor, - [config_handler, new_administrative_state, mr_pub, app, - mr_aai_event_subscriber]).start() +from mod.subscription_handler import SubscriptionHandler +from mod.aai_event_handler import process_aai_events def main(): @@ -81,21 +38,24 @@ def main(): app.app_context().push() db.create_all(app=app) sub, nfs = aai.get_pmsh_subscription_data(config) - mr_pub = app_conf.get_mr_pub('policy_pm_publisher') - mr_sub = app_conf.get_mr_sub('policy_pm_subscriber') - mr_aai_event_subscriber = app_conf.get_mr_sub('aai_subscriber') - initial_start_delay = 5.0 - - administrative_state = AdministrativeState.LOCKED.value + policy_mr_pub = app_conf.get_mr_pub('policy_pm_publisher') + policy_mr_sub = app_conf.get_mr_sub('policy_pm_subscriber') + mr_aai_event_sub = app_conf.get_mr_sub('aai_subscriber') subscription_in_db = Subscription.get(sub.subscriptionName) - if subscription_in_db is not None: - administrative_state = subscription_in_db.status + administrative_state = subscription_in_db.status if subscription_in_db \ + else AdministrativeState.LOCKED.value + + aai_event_thread = PeriodicTask(10, process_aai_events, + args=(mr_aai_event_sub, sub, policy_mr_pub, app, app_conf)) + subscription_handler = SubscriptionHandler(config_handler, administrative_state, + policy_mr_pub, aai_event_thread, app, app_conf) + policy_response_handler = PolicyResponseHandler(policy_mr_sub, sub.subscriptionName, app) - threading.Timer(initial_start_delay, subscription_processor, - [config_handler, administrative_state, mr_pub, - app, mr_aai_event_subscriber]).start() + subscription_handler_thread = PeriodicTask(30, subscription_handler.execute) + policy_response_handler_thread = PeriodicTask(5, policy_response_handler.poll_policy_topic) - threading.Timer(20.0, mr_sub.poll_policy_topic, [sub.subscriptionName, app]).start() + subscription_handler_thread.start() + policy_response_handler_thread.start() launch_api_server(app_conf) diff --git a/components/pm-subscription-handler/pom.xml b/components/pm-subscription-handler/pom.xml index 04d6fac4..64d35056 100644 --- a/components/pm-subscription-handler/pom.xml +++ b/components/pm-subscription-handler/pom.xml @@ -31,7 +31,7 @@ <groupId>org.onap.dcaegen2.services</groupId> <artifactId>pmsh</artifactId> <name>dcaegen2-services-pm-subscription-handler</name> - <version>1.0.1-SNAPSHOT</version> + <version>1.0.2-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <sonar.sources>.</sonar.sources> diff --git a/components/pm-subscription-handler/tests/test_healthcheck.py b/components/pm-subscription-handler/tests/test_healthcheck.py index 6e960d05..1c40c3ff 100755 --- a/components/pm-subscription-handler/tests/test_healthcheck.py +++ b/components/pm-subscription-handler/tests/test_healthcheck.py @@ -18,7 +18,7 @@ import unittest -from pmsh_service.mod.healthcheck import status +from mod.healthcheck import status class HealthcheckTestCase(unittest.TestCase): diff --git a/components/pm-subscription-handler/tests/test_pmsh_service.py b/components/pm-subscription-handler/tests/test_pmsh_service.py deleted file mode 100644 index cd28a5d9..00000000 --- a/components/pm-subscription-handler/tests/test_pmsh_service.py +++ /dev/null @@ -1,88 +0,0 @@ -# ============LICENSE_START=================================================== -# Copyright (C) 2020 Nordix Foundation. -# ============================================================================ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# ============LICENSE_END===================================================== -import os -import json -from unittest import TestCase -from unittest.mock import patch - -import pmsh_service_main as pmsh_service -from mod.network_function import NetworkFunction - - -class PMSHServiceTest(TestCase): - - @patch('mod.create_app') - @patch('mod.subscription.Subscription') - @patch('mod.pmsh_utils._MrPub') - @patch('mod.config_handler.ConfigHandler') - def setUp(self, mock_config_handler, mock_mr_pub, - mock_sub, mock_app): - with open(os.path.join(os.path.dirname(__file__), 'data/cbs_data_1.json'), 'r') as data: - self.cbs_data_1 = json.load(data) - self.mock_app = mock_app - self.mock_sub = mock_sub - self.mock_mr_pub = mock_mr_pub - self.mock_config_handler = mock_config_handler - self.mock_aai_sub = mock_sub - self.nf_1 = NetworkFunction(nf_name='pnf_1') - self.nf_2 = NetworkFunction(nf_name='pnf_2') - self.nfs = [self.nf_1, self.nf_2] - - @patch('threading.Timer') - @patch('mod.aai_client.get_pmsh_subscription_data') - @patch('pmsh_service_main.PeriodicTask') - @patch('pmsh_service_main.AppConfig') - def test_subscription_processor_changed_state(self, mock_app_conf, periodic_task, mock_get_aai, - mock_thread): - self.mock_config_handler.get_config.return_value = self.cbs_data_1 - mock_get_aai.return_value = self.mock_sub, self.nfs - mock_thread.start.return_value = 1 - periodic_task.start.return_value = 1 - - pmsh_service.subscription_processor(self.mock_config_handler, 'LOCKED', - self.mock_mr_pub, self.mock_app, self.mock_aai_sub) - - self.mock_sub.process_subscription.assert_called_with(self.nfs, self.mock_mr_pub, - mock_app_conf.return_value) - - @patch('threading.Timer') - @patch('mod.pmsh_logging.debug') - @patch('mod.aai_client.get_pmsh_subscription_data') - def test_subscription_processor_unchanged_state(self, mock_get_aai, mock_logger, mock_thread): - self.mock_config_handler.get_config.return_value = self.cbs_data_1 - mock_get_aai.return_value = self.mock_sub, self.nfs - mock_thread.start.return_value = 1 - - pmsh_service.subscription_processor(self.mock_config_handler, 'UNLOCKED', self.mock_mr_pub, - self.mock_app, self.mock_aai_sub) - - mock_logger.assert_called_with('Administrative State did not change in the Config') - - @patch('threading.Timer') - @patch('mod.pmsh_logging.debug') - @patch('mod.aai_client.get_pmsh_subscription_data') - def test_subscription_processor_exception(self, mock_get_aai, mock_logger, mock_thread): - self.mock_config_handler.get_config.return_value = self.cbs_data_1 - mock_get_aai.return_value = self.mock_sub, self.nfs - mock_thread.start.return_value = 1 - self.mock_sub.process_subscription.side_effect = Exception - - pmsh_service.subscription_processor(self.mock_config_handler, 'LOCKED', self.mock_mr_pub, - self.mock_app, self.mock_aai_sub) - mock_logger.assert_called_with(f'Error occurred during the ' - f'activation/deactivation process ') diff --git a/components/pm-subscription-handler/tests/test_pmsh_utils.py b/components/pm-subscription-handler/tests/test_pmsh_utils.py index ea657f49..236331b4 100644 --- a/components/pm-subscription-handler/tests/test_pmsh_utils.py +++ b/components/pm-subscription-handler/tests/test_pmsh_utils.py @@ -23,13 +23,10 @@ from unittest.mock import patch import responses from requests import Session -from tenacity import stop_after_attempt from mod import db, get_db_connection_url, create_app -from mod.db_models import SubscriptionModel -from mod.pmsh_utils import AppConfig, policy_response_handle_functions +from mod.pmsh_utils import AppConfig from mod.subscription import Subscription -from mod.network_function import NetworkFunction class PmshUtilsTestCase(TestCase): @@ -126,93 +123,3 @@ class PmshUtilsTestCase(TestCase): self.env.set('PMSH_PG_PASSWORD', 'pass') with self.assertRaises(Exception): get_db_connection_url() - - @patch('mod.pmsh_utils.NetworkFunction.delete') - def test_handle_response_locked_success(self, mock_delete): - with patch.dict(policy_response_handle_functions, {'LOCKED': {'success': mock_delete}}): - administrative_state = 'LOCKED' - nf = NetworkFunction(nf_name='nf1') - self.policy_mr_sub._handle_response(self.sub.subscriptionName, administrative_state, - nf.nf_name, 'success') - - mock_delete.assert_called() - - @patch('mod.subscription.Subscription.update_sub_nf_status') - def test_handle_response_locked_failed(self, mock_update_sub_nf): - with patch.dict(policy_response_handle_functions, - {'LOCKED': {'failed': mock_update_sub_nf}}): - administrative_state = 'LOCKED' - nf = NetworkFunction(nf_name='nf1') - self.policy_mr_sub._handle_response(self.sub.subscriptionName, administrative_state, - nf.nf_name, 'failed') - mock_update_sub_nf.assert_called() - - @patch('mod.subscription.Subscription.update_sub_nf_status') - def test_handle_response_unlocked_success(self, mock_update_sub_nf): - with patch.dict(policy_response_handle_functions, - {'UNLOCKED': {'success': mock_update_sub_nf}}): - nf = NetworkFunction(nf_name='nf1') - self.policy_mr_sub._handle_response(self.sub.subscriptionName, - self.sub.administrativeState, - nf.nf_name, 'success') - mock_update_sub_nf.assert_called() - - @patch('mod.subscription.Subscription.update_sub_nf_status') - def test_handle_response_unlocked_failed(self, mock_update_sub_nf): - with patch.dict(policy_response_handle_functions, - {'UNLOCKED': {'failed': mock_update_sub_nf}}): - nf = NetworkFunction(nf_name='nf1') - self.policy_mr_sub._handle_response(self.sub.subscriptionName, - self.sub.administrativeState, - nf.nf_name, 'failed') - mock_update_sub_nf.assert_called() - - def test_handle_response_exception(self): - self.assertRaises(Exception, self.policy_mr_sub._handle_response, 'sub1', 'wrong_state', - 'nf1', 'wrong_message') - - @patch('mod.pmsh_utils._MrSub.get_from_topic') - @patch('mod.pmsh_utils._MrSub._handle_response') - @patch('mod.subscription.Subscription.get') - @patch('threading.Timer') - def test_poll_policy_topic_calls_methods_correct_sub(self, mock_thread, mock_get_sub, - mock_handle_response, mock_get_from_topic): - result_data = ['{"name": "ResponseEvent","status": { "subscriptionName": ' - '"ExtraPM-All-gNB-R2B", "nfName": "pnf300", "message": "success" } }'] - mock_get_from_topic.return_value = result_data - mock_thread.start.return_value = 1 - mock_get_sub.return_value = SubscriptionModel(subscription_name='ExtraPM-All-gNB-R2B', - status='UNLOCKED') - self.policy_mr_sub.poll_policy_topic(self.sub.subscriptionName, self.mock_app) - - mock_get_from_topic.assert_called() - mock_handle_response.assert_called_with(self.sub.subscriptionName, - 'UNLOCKED', 'pnf300', 'success') - - @patch('mod.pmsh_utils._MrSub.get_from_topic') - @patch('mod.pmsh_utils._MrSub._handle_response') - @patch('mod.subscription.Subscription.get') - @patch('threading.Timer') - def test_poll_policy_topic_no_method_calls_incorrect_sub(self, mock_thread, mock_get_sub, - mock_handle_response, - mock_get_from_topic): - result_data = ['{"name": "ResponseEvent","status": { "subscriptionName": ' - '"demo-subscription", "nfName": "pnf300", "message": "success" } }'] - mock_get_from_topic.return_value = result_data - mock_thread.start.return_value = 1 - mock_get_sub.return_value = SubscriptionModel(subscription_name='ExtraPM-All-gNB-R2B', - status='UNLOCKED') - self.policy_mr_sub.poll_policy_topic(self.sub, self.mock_app) - - mock_get_from_topic.assert_called() - mock_handle_response.assert_not_called() - - @patch('mod.subscription.Subscription.get') - @patch('mod.pmsh_utils._MrSub.get_from_topic') - def test_poll_policy_topic_exception(self, mock_get_from_topic, mock_get_sub): - mock_get_from_topic.return_value = 'wrong_return' - mock_get_sub.return_value = SubscriptionModel(subscription_name='ExtraPM-All-gNB-R2B', - status='UNLOCKED') - self.policy_mr_sub.poll_policy_topic.retry.stop = stop_after_attempt(1) - - self.assertRaises(Exception, self.policy_mr_sub.poll_policy_topic, 'sub1', self.mock_app) diff --git a/components/pm-subscription-handler/tests/test_policy_response_handler.py b/components/pm-subscription-handler/tests/test_policy_response_handler.py new file mode 100644 index 00000000..1cf947fb --- /dev/null +++ b/components/pm-subscription-handler/tests/test_policy_response_handler.py @@ -0,0 +1,133 @@ +# ============LICENSE_START=================================================== +# Copyright (C) 2019-2020 Nordix Foundation. +# ============================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END===================================================== +import json +import os +from unittest import TestCase +from unittest.mock import patch + +from tenacity import stop_after_attempt + +from mod.db_models import SubscriptionModel +from mod.network_function import NetworkFunction +from mod.subscription import AdministrativeState, SubNfState +from mod.policy_response_handler import PolicyResponseHandler, policy_response_handle_functions + + +class PolicyResponseHandlerTest(TestCase): + + @patch('mod.create_app') + @patch('mod.subscription.Subscription') + @patch('mod.pmsh_utils._MrSub') + def setUp(self, mock_mr_sub, mock_sub, mock_app): + with open(os.path.join(os.path.dirname(__file__), 'data/cbs_data_1.json'), 'r') as data: + self.cbs_data = json.load(data) + self.mock_policy_mr_sub = mock_mr_sub + self.mock_sub = mock_sub + self.mock_sub.subscriptionName = 'ExtraPM-All-gNB-R2B' + self.mock_app = mock_app + self.nf = NetworkFunction(nf_name='nf1') + self.policy_response_handler = PolicyResponseHandler(self.mock_policy_mr_sub, + self.mock_sub.subscriptionName, + self.mock_app) + + @patch('mod.network_function.NetworkFunction.delete') + def test_handle_response_locked_success(self, mock_delete): + with patch.dict(policy_response_handle_functions, + {AdministrativeState.LOCKED.value: {'success': mock_delete}}): + self.policy_response_handler._handle_response(self.mock_sub.subscriptionName, + AdministrativeState.LOCKED.value, + self.nf.nf_name, 'success') + + mock_delete.assert_called() + + @patch('mod.subscription.Subscription.update_sub_nf_status') + def test_handle_response_locked_failed(self, mock_update_sub_nf): + with patch.dict(policy_response_handle_functions, + {AdministrativeState.LOCKED.value: {'failed': mock_update_sub_nf}}): + self.policy_response_handler._handle_response(self.mock_sub.subscriptionName, + AdministrativeState.LOCKED.value, + self.nf.nf_name, 'failed') + mock_update_sub_nf.assert_called_with(subscription_name=self.mock_sub.subscriptionName, + status=SubNfState.DELETE_FAILED.value, + nf_name=self.nf.nf_name) + + @patch('mod.subscription.Subscription.update_sub_nf_status') + def test_handle_response_unlocked_success(self, mock_update_sub_nf): + with patch.dict(policy_response_handle_functions, + {AdministrativeState.UNLOCKED.value: {'success': mock_update_sub_nf}}): + self.policy_response_handler._handle_response(self.mock_sub.subscriptionName, + AdministrativeState.UNLOCKED.value, + self.nf.nf_name, 'success') + mock_update_sub_nf.assert_called_with(subscription_name=self.mock_sub.subscriptionName, + status=SubNfState.CREATED.value, + nf_name=self.nf.nf_name) + + @patch('mod.subscription.Subscription.update_sub_nf_status') + def test_handle_response_unlocked_failed(self, mock_update_sub_nf): + with patch.dict(policy_response_handle_functions, + {AdministrativeState.UNLOCKED.value: {'failed': mock_update_sub_nf}}): + self.policy_response_handler._handle_response(self.mock_sub.subscriptionName, + AdministrativeState.UNLOCKED.value, + self.nf.nf_name, 'failed') + mock_update_sub_nf.assert_called_with(subscription_name=self.mock_sub.subscriptionName, + status=SubNfState.CREATE_FAILED.value, + nf_name=self.nf.nf_name) + + def test_handle_response_exception(self): + self.assertRaises(Exception, self.policy_response_handler._handle_response, 'sub1', + 'wrong_state', 'nf1', 'wrong_message') + + @patch('mod.policy_response_handler.PolicyResponseHandler._handle_response') + @patch('mod.subscription.Subscription.get') + def test_poll_policy_topic_calls_methods_correct_sub(self, mock_get_sub, mock_handle_response): + response_data = ['{"name": "ResponseEvent","status": { "subscriptionName": ' + '"ExtraPM-All-gNB-R2B", "nfName": "pnf300", "message": "success" } }'] + self.mock_policy_mr_sub.get_from_topic.return_value = response_data + mock_get_sub.return_value = SubscriptionModel(subscription_name='ExtraPM-All-gNB-R2B', + status=AdministrativeState.UNLOCKED.value) + self.policy_response_handler.poll_policy_topic() + + self.mock_policy_mr_sub.get_from_topic.assert_called() + + mock_handle_response.assert_called_with(self.mock_sub.subscriptionName, + AdministrativeState.UNLOCKED.value, 'pnf300', + 'success') + + @patch('mod.policy_response_handler.PolicyResponseHandler._handle_response') + @patch('mod.subscription.Subscription.get') + def test_poll_policy_topic_no_method_calls_incorrect_sub(self, mock_get_sub, + mock_handle_response): + response_data = ['{"name": "ResponseEvent","status": { "subscriptionName": ' + '"Different_Subscription", "nfName": "pnf300", "message": "success" } }'] + self.mock_policy_mr_sub.get_from_topic.return_value = response_data + mock_get_sub.return_value = SubscriptionModel(subscription_name='ExtraPM-All-gNB-R2B', + status=AdministrativeState.UNLOCKED.value) + self.policy_response_handler.poll_policy_topic() + + self.mock_policy_mr_sub.get_from_topic.assert_called() + + mock_handle_response.assert_not_called() + + @patch('mod.subscription.Subscription.get') + def test_poll_policy_topic_exception(self, mock_get_sub): + self.mock_policy_mr_sub.get_from_topic.return_value = 'wrong_return' + mock_get_sub.return_value = SubscriptionModel(subscription_name='ExtraPM-All-gNB-R2B', + status=AdministrativeState.UNLOCKED.value) + self.policy_response_handler.poll_policy_topic.retry.stop = stop_after_attempt(1) + + self.assertRaises(Exception, self.policy_response_handler.poll_policy_topic) diff --git a/components/pm-subscription-handler/tests/test_subscription.py b/components/pm-subscription-handler/tests/test_subscription.py index bd39f28a..d152863d 100755 --- a/components/pm-subscription-handler/tests/test_subscription.py +++ b/components/pm-subscription-handler/tests/test_subscription.py @@ -26,9 +26,9 @@ from tenacity import stop_after_attempt import mod.aai_client as aai_client from mod import db, create_app -from mod.network_function import NetworkFunction +from mod.network_function import NetworkFunction, NetworkFunctionFilter from mod.pmsh_utils import AppConfig -from mod.subscription import Subscription, NetworkFunctionFilter +from mod.subscription import Subscription class SubscriptionTest(TestCase): diff --git a/components/pm-subscription-handler/tests/test_subscription_handler.py b/components/pm-subscription-handler/tests/test_subscription_handler.py new file mode 100644 index 00000000..0eed7c45 --- /dev/null +++ b/components/pm-subscription-handler/tests/test_subscription_handler.py @@ -0,0 +1,105 @@ +# ============LICENSE_START=================================================== +# Copyright (C) 2020 Nordix Foundation. +# ============================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END===================================================== +import os +import json +from unittest import TestCase +from unittest.mock import patch + +from mod.subscription_handler import SubscriptionHandler +from mod.subscription import AdministrativeState +from mod.network_function import NetworkFunction + + +class SubscriptionHandlerTest(TestCase): + + @patch('mod.create_app') + @patch('mod.subscription.Subscription') + @patch('mod.pmsh_utils._MrPub') + @patch('mod.pmsh_utils.PeriodicTask') + @patch('mod.config_handler.ConfigHandler') + @patch('mod.pmsh_utils.AppConfig') + def setUp(self, mock_app_conf, mock_config_handler, mock_aai_thread, mock_mr_pub, + mock_sub, mock_app): + with open(os.path.join(os.path.dirname(__file__), 'data/cbs_data_1.json'), 'r') as data: + self.cbs_data_1 = json.load(data) + self.mock_app = mock_app + self.mock_sub = mock_sub + self.mock_mr_pub = mock_mr_pub + self.mock_aai_thread = mock_aai_thread + self.mock_config_handler = mock_config_handler + self.mock_app_conf = mock_app_conf + self.nf_1 = NetworkFunction(nf_name='pnf_1') + self.nf_2 = NetworkFunction(nf_name='pnf_2') + self.nfs = [self.nf_1, self.nf_2] + + @patch('mod.pmsh_logging.debug') + @patch('mod.aai_client.get_pmsh_subscription_data') + def test_execute_no_change_of_state(self, mock_get_aai, mock_logger): + mock_get_aai.return_value = self.mock_sub, self.nfs + self.mock_config_handler.get_config.return_value = self.cbs_data_1 + sub_handler = SubscriptionHandler(self.mock_config_handler, + AdministrativeState.UNLOCKED.value, self.mock_mr_pub, + self.mock_aai_thread, self.mock_app, self.mock_app_conf) + sub_handler.execute() + + mock_logger.assert_called_with('Administrative State did not change in the Config') + + @patch('mod.aai_client.get_pmsh_subscription_data') + def test_execute_change_of_state_unlocked(self, mock_get_aai): + mock_get_aai.return_value = self.mock_sub, self.nfs + self.mock_aai_thread.return_value.start.return_value = 'start_method' + self.mock_config_handler.get_config.return_value = self.cbs_data_1 + sub_handler = SubscriptionHandler(self.mock_config_handler, + AdministrativeState.LOCKED.value, self.mock_mr_pub, + self.mock_aai_thread, self.mock_app, self.mock_app_conf) + sub_handler.execute() + + self.assertEqual(AdministrativeState.UNLOCKED.value, sub_handler.administrative_state) + self.mock_sub.process_subscription.assert_called_with(self.nfs, self.mock_mr_pub, + self.mock_app_conf) + self.mock_aai_thread.start.assert_called() + + @patch('mod.aai_client.get_pmsh_subscription_data') + def test_execute_change_of_state_locked(self, mock_get_aai): + mock_get_aai.return_value = self.mock_sub, self.nfs + self.mock_aai_thread.return_value.cancel.return_value = 'cancel_method' + self.cbs_data_1['policy']['subscription']['administrativeState'] = \ + AdministrativeState.LOCKED.value + self.mock_config_handler.get_config.return_value = self.cbs_data_1 + sub_handler = SubscriptionHandler(self.mock_config_handler, + AdministrativeState.UNLOCKED.value, self.mock_mr_pub, + self.mock_aai_thread, self.mock_app, self.mock_app_conf) + sub_handler.execute() + + self.assertEqual(AdministrativeState.LOCKED.value, sub_handler.administrative_state) + self.mock_sub.process_subscription.assert_called_with(self.nfs, self.mock_mr_pub, + self.mock_app_conf) + self.mock_aai_thread.cancel.assert_called() + + @patch('mod.pmsh_logging.debug') + @patch('mod.aai_client.get_pmsh_subscription_data') + def test_execute_exception(self, mock_get_aai, mock_logger): + mock_get_aai.return_value = self.mock_sub, self.nfs + self.mock_config_handler.get_config.return_value = self.cbs_data_1 + self.mock_sub.process_subscription.side_effect = Exception + sub_handler = SubscriptionHandler(self.mock_config_handler, + AdministrativeState.LOCKED.value, self.mock_mr_pub, + self.mock_aai_thread, self.mock_app, self.mock_app_conf) + sub_handler.execute() + + mock_logger.assert_called_with('Error occurred during the activation/deactivation process ') diff --git a/components/pm-subscription-handler/version.properties b/components/pm-subscription-handler/version.properties index 0f1f46a5..c13587b4 100644 --- a/components/pm-subscription-handler/version.properties +++ b/components/pm-subscription-handler/version.properties @@ -1,6 +1,6 @@ major=1 minor=0 -patch=1 +patch=2 base_version=${major}.${minor}.${patch} release_version=${base_version} snapshot_version=${base_version}-SNAPSHOT |