diff options
32 files changed, 1692 insertions, 1050 deletions
diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..9931ea93 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,36 @@ +/* +* ============LICENSE_START========================================== +* =================================================================== +* Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. +* =================================================================== +* +* Unless otherwise specified, all software contained herein is licensed +* under the Apache License, Version 2.0 (the "License"); +* you may not use this software 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. +* +* +* +* Unless otherwise specified, all documentation contained herein is licensed +* under the Creative Commons License, Attribution 4.0 Intl. (the "License"); +* you may not use this documentation except in compliance with the License. +* You may obtain a copy of the License at +* +* https://creativecommons.org/licenses/by/4.0/ +* +* Unless required by applicable law or agreed to in writing, documentation +* 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. +* +* ============LICENSE_END============================================ +*/ diff --git a/components/datalake-handler/LICENSE.txt b/components/datalake-handler/LICENSE.txt new file mode 100644 index 00000000..b12df03b --- /dev/null +++ b/components/datalake-handler/LICENSE.txt @@ -0,0 +1,36 @@ +/* +* ============LICENSE_START========================================== +* =================================================================== +* Copyright (c) 2019 ChinaMobile Intellectual Property. All rights reserved. +* =================================================================== +* +* Unless otherwise specified, all software contained herein is licensed +* under the Apache License, Version 2.0 (the "License"); +* you may not use this software 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. +* +* +* +* Unless otherwise specified, all documentation contained herein is licensed +* under the Creative Commons License, Attribution 4.0 Intl. (the "License"); +* you may not use this documentation except in compliance with the License. +* You may obtain a copy of the License at +* +* https://creativecommons.org/licenses/by/4.0/ +* +* Unless required by applicable law or agreed to in writing, documentation +* 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. +* +* ============LICENSE_END============================================ +*/ diff --git a/components/datalake-handler/admin/src/LICENSE b/components/datalake-handler/admin/src/LICENSE deleted file mode 100644 index d6456956..00000000 --- a/components/datalake-handler/admin/src/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/components/kpi-computation-ms/LICENSE.txt b/components/kpi-computation-ms/LICENSE.txt new file mode 100644 index 00000000..be3117cb --- /dev/null +++ b/components/kpi-computation-ms/LICENSE.txt @@ -0,0 +1,36 @@ +/* +* ============LICENSE_START========================================== +* =================================================================== +* Copyright (c) 2021 ChinaMobile Intellectual Property. All rights reserved. +* =================================================================== +* +* Unless otherwise specified, all software contained herein is licensed +* under the Apache License, Version 2.0 (the "License"); +* you may not use this software 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. +* +* +* +* Unless otherwise specified, all documentation contained herein is licensed +* under the Creative Commons License, Attribution 4.0 Intl. (the "License"); +* you may not use this documentation except in compliance with the License. +* You may obtain a copy of the License at +* +* https://creativecommons.org/licenses/by/4.0/ +* +* Unless required by applicable law or agreed to in writing, documentation +* 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. +* +* ============LICENSE_END============================================ +*/ diff --git a/components/pm-subscription-handler/Changelog.md b/components/pm-subscription-handler/Changelog.md index f02fc5eb..db096b11 100755 --- a/components/pm-subscription-handler/Changelog.md +++ b/components/pm-subscription-handler/Changelog.md @@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Implemented Create Subscription public API (DCAEGEN2-2819) * 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) +* 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) +* Read NFS associated with MG by using MGName and subName(DCAEGEN2-2993) ## [1.3.2] ### Changed diff --git a/components/pm-subscription-handler/pmsh_service/mod/aai_event_handler.py b/components/pm-subscription-handler/pmsh_service/mod/aai_event_handler.py index a00c164a..fe4166e7 100755 --- a/components/pm-subscription-handler/pmsh_service/mod/aai_event_handler.py +++ b/components/pm-subscription-handler/pmsh_service/mod/aai_event_handler.py @@ -15,12 +15,14 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== - import json from enum import Enum - -from mod import logger +from mod import logger, db from mod.network_function import NetworkFunction +from mod.pmsh_config import AppConfig, MRTopic +from mod.api.db_models import SubscriptionModel +from mod.network_function import NetworkFunctionFilter +from mod.api.services import subscription_service class XNFType(Enum): @@ -33,53 +35,98 @@ class AAIEvent(Enum): UPDATE = 'UPDATE' -def process_aai_events(mr_sub, mr_pub, app, app_conf): +def is_pnf_xnf(entity_type): """ - Processes AAI UPDATE events for each filtered xNFs where orchestration status is set to Active. - + Applies measurement groups to network functions identified by AAI event Args: - mr_sub (_MrSub): MR subscriber - mr_pub (_MrPub): MR publisher - app (db): DB application - app_conf (AppConfig): the application configuration. + entity_type (string): The type of network function """ - app.app_context().push() - logger.info('Polling MR for XNF AAI events.') - try: - aai_events = mr_sub.get_from_topic('dcae_pmsh_aai_event') - if aai_events is not None and len(aai_events) != 0: - aai_events = [json.loads(e) for e in aai_events] - xnf_events = [e for e in aai_events if e['event-header']['entity-type'] == ( - XNFType.PNF.value or XNFType.VNF.value)] - for entry in xnf_events: - logger.debug(f'AAI-EVENT entry: {entry}') - aai_entity = entry['entity'] - action = entry['event-header']['action'] - entity_type = entry['event-header']['entity-type'] - xnf_name = aai_entity['pnf-name'] if entity_type == XNFType.PNF.value \ - else aai_entity['vnf-name'] - if aai_entity['orchestration-status'] != 'Active': - logger.info(f'Skipping XNF {xnf_name} as its orchestration-status ' - f'is not "Active"') - continue - nf = NetworkFunction(nf_name=xnf_name, - ipv4_address=aai_entity['ipaddress-v4-oam'], - ipv6_address=aai_entity['ipaddress-v6-oam'], - model_invariant_id=aai_entity['model-invariant-id'], - model_version_id=aai_entity['model-version-id']) - if not nf.set_nf_model_params(app_conf): - continue - if app_conf.nf_filter.is_nf_in_filter(nf): - _process_event(action, nf, mr_pub, app_conf) - except Exception as e: - logger.error(f'Failed to process AAI event: {e}', exc_info=True) + return entity_type == (XNFType.PNF.value or XNFType.VNF.value) + + +class AAIEventHandler: + """ Responsible for handling AAI update events in PMSH """ + + def __init__(self, app): + self.app = app + def execute(self): + """ + Processes AAI UPDATE events for each filtered xNFs where + orchestration status is set to Active. + """ + self.app.app_context().push() + logger.info('Polling MR for XNF AAI events.') + try: + aai_events = AppConfig.get_instance().get_from_topic(MRTopic.AAI_SUBSCRIBER.value, + 'dcae_pmsh_aai_event') + if aai_events is not None and len(aai_events) != 0: + pmsh_nf_names = list(nf.nf_name for nf in NetworkFunction.get_all()) + aai_events = [json.loads(e) for e in aai_events] + xnf_events = [e for e in aai_events if is_pnf_xnf(e['event-header']['entity-type'])] + new_nfs = [] + for entry in xnf_events: + logger.debug(f'AAI-EVENT entry: {entry}') + aai_entity = entry['entity'] + action = entry['event-header']['action'] + entity_type = entry['event-header']['entity-type'] + xnf_name = aai_entity['pnf-name'] if entity_type == XNFType.PNF.value \ + else aai_entity['vnf-name'] + if aai_entity['orchestration-status'] != 'Active': + logger.info(f'Skipping XNF {xnf_name} as its orchestration-status ' + f'is not "Active"') + continue + nf = NetworkFunction(nf_name=xnf_name, + ipv4_address=aai_entity['ipaddress-v4-oam'], + ipv6_address=aai_entity['ipaddress-v6-oam'], + model_invariant_id=aai_entity['model-invariant-id'], + model_version_id=aai_entity['model-version-id']) + if action == AAIEvent.DELETE.value and xnf_name in pmsh_nf_names: + logger.info(f'Delete event found for network function {nf.nf_name}') + NetworkFunction.delete(nf_name=nf.nf_name) + logger.info(f'{nf.nf_name} successfully deleted.') + elif action == AAIEvent.UPDATE.value and \ + xnf_name not in pmsh_nf_names and \ + nf.set_nf_model_params(AppConfig.get_instance()): + new_nfs.append(nf) + if new_nfs: + self.apply_nfs_to_subscriptions(new_nfs) + except Exception as e: + logger.error(f'Failed to process AAI event due to: {e}') -def _process_event(action, nf, mr_pub, app_conf): - if action == AAIEvent.UPDATE.value: - logger.info(f'Update event found for network function {nf.nf_name}') - app_conf.subscription.create_subscription_on_nfs([nf], mr_pub) - elif action == AAIEvent.DELETE.value: - logger.info(f'Delete event found for network function {nf.nf_name}') - NetworkFunction.delete(nf_name=nf.nf_name) - logger.info(f'{nf.nf_name} successfully deleted.') + @staticmethod + def apply_nfs_to_subscriptions(new_nfs): + """ + Applies measurement groups to network functions identified by AAI event + Args: + new_nfs (list[NetworkFunction]): new network functions identified + """ + subscriptions = db.session.query(SubscriptionModel).all() + if subscriptions: + for subscription in subscriptions: + try: + nf_filter = NetworkFunctionFilter(**subscription.network_filter.serialize()) + filtered_nfs = [] + for nf in new_nfs: + if nf_filter.is_nf_in_filter(nf): + filtered_nfs.append(nf) + if filtered_nfs: + subscription_service.save_filtered_nfs(filtered_nfs) + subscription_service. \ + apply_subscription_to_nfs(filtered_nfs, subscription.subscription_name) + unlocked_meas_grp = subscription_service. \ + apply_measurement_grp_to_nfs(filtered_nfs, + subscription.measurement_groups) + if unlocked_meas_grp: + subscription_service. \ + publish_measurement_grp_to_nfs(subscription, filtered_nfs, + unlocked_meas_grp) + else: + logger.error(f'All measurement groups are locked for subscription: ' + f'{subscription.subscription_name}, ' + f'please verify/check measurement groups.') + db.session.commit() + except Exception as e: + logger.error(f'Failed to process AAI event for subscription: ' + f'{subscription.subscription_name} due to: {e}') + db.session.remove() diff --git a/components/pm-subscription-handler/pmsh_service/mod/api/controller.py b/components/pm-subscription-handler/pmsh_service/mod/api/controller.py index 80d86362..aee6df0e 100755 --- a/components/pm-subscription-handler/pmsh_service/mod/api/controller.py +++ b/components/pm-subscription-handler/pmsh_service/mod/api/controller.py @@ -18,7 +18,7 @@ from http import HTTPStatus from mod import logger -from mod.api.services import subscription_service +from mod.api.services import subscription_service, measurement_group_service from connexion import NoContent from mod.api.custom_exception import InvalidDataException, DuplicateDataException @@ -75,24 +75,75 @@ def get_subscription_by_name(subscription_name): subscription_name (String): Name of the subscription. Returns: - success: dict of single Subscription, 200 - None: subscription not defined, 404 - Exception: Details about exception, 500 + dict, HTTPStatus: single Sub in PMSH, 200 + dict, HTTPStatus: subscription not defined, 404 + dict, HTTPStatus: Exception details of failure, 500 """ logger.info('API call received to fetch subscription by name') try: - subscription = subscription_service.get_subscription_by_name(subscription_name) + subscription = subscription_service.query_subscription_by_name(subscription_name) if subscription is not None: - logger.info(f'subscription object with the name "{subscription_name}" ' - 'was fetched successfully from database') - return subscription.serialize(), HTTPStatus.OK + logger.info(f'queried subscription was successful with the name: {subscription_name}') + return subscription.serialize(), HTTPStatus.OK.value else: - logger.error(f'subscription object with the name "{subscription_name}" ' - 'was un successful to fetch from database') + logger.error('queried subscription was un successful with the name: ' + f'{subscription_name}') return {'error': 'Subscription was not defined with the name : ' - f'{subscription_name}'}, HTTPStatus.NOT_FOUND + f'{subscription_name}'}, HTTPStatus.NOT_FOUND.value except Exception as exception: - logger.error(f'The following exception occurred "{exception}" while fetching subscription ' - f'with the name "{subscription_name}"') + logger.error(f'While querying the subscription with name: {subscription_name}, ' + f'it occurred the following exception "{exception}"') return {'error': 'Request was not processed due to Exception : ' - f'{exception}'}, HTTPStatus.INTERNAL_SERVER_ERROR + f'{exception}'}, HTTPStatus.INTERNAL_SERVER_ERROR.value + + +def get_subscriptions(): + """ Retrieves all the subscriptions that are defined in PMSH. + + Returns: + list (dict), HTTPStatus: All subs in PMSH, 200 + dict, HTTPStatus: Exception details of failure, 500 + """ + logger.info('API call received to fetch all subscriptions') + try: + subscriptions = subscription_service.get_subscriptions_list() + return subscriptions, HTTPStatus.OK.value + except Exception as exception: + logger.error(f'The following exception occurred while fetching subscriptions: {exception}') + return {'error': 'Request was not processed due to Exception : ' + f'{exception}'}, HTTPStatus.INTERNAL_SERVER_ERROR.value + + +def get_meas_group_with_nfs(subscription_name, measurement_group_name): + """ + Retrieves the measurement group and it's associated network functions + + Args: + subscription_name (String): Name of the subscription. + measurement_group_name (String): Name of the measurement group + + Returns: + dict, HTTPStatus: measurement group info with associated nfs, 200 + dict, HTTPStatus: measurement group was not defined, 404 + dict, HTTPStatus: Exception details of failure, 500 + """ + logger.info('API call received to query measurement group and associated network' + f' functions by using sub name: {subscription_name} and measurement ' + f'group name: {measurement_group_name}') + try: + meas_group = measurement_group_service.query_meas_group_by_name(subscription_name, + measurement_group_name) + if meas_group is not None: + return meas_group.meas_group_with_nfs(), HTTPStatus.OK.value + else: + logger.error('measurement group was not defined with the sub name: ' + f'{subscription_name} and meas group name: ' + f'{measurement_group_name}') + return {'error': 'measurement group was not defined with the sub name: ' + f'{subscription_name} and meas group name: ' + f'{measurement_group_name}'}, HTTPStatus.NOT_FOUND.value + except Exception as exception: + logger.error('The following exception occurred while fetching measurement group: ' + f'{exception}') + return {'error': 'Request was not processed due to Exception : ' + f'{exception}'}, HTTPStatus.INTERNAL_SERVER_ERROR.value diff --git a/components/pm-subscription-handler/pmsh_service/mod/api/db_models.py b/components/pm-subscription-handler/pmsh_service/mod/api/db_models.py index 96a803ba..548e4f28 100755 --- a/components/pm-subscription-handler/pmsh_service/mod/api/db_models.py +++ b/components/pm-subscription-handler/pmsh_service/mod/api/db_models.py @@ -52,10 +52,10 @@ class SubscriptionModel(db.Model): self.status = status def __repr__(self): - return f'subscription_name: {self.subscription_name},' \ - f'operational_policy_name: {self.operational_policy_name},' \ - f'control_loop_name: {self.control_loop_name},' \ - f'status: {self.status}' + return (f'subscription_name: {self.subscription_name}, ' + f'operational_policy_name: {self.operational_policy_name}, ' + f'control_loop_name: {self.control_loop_name}, ' + f'status: {self.status}') def __eq__(self, other): if isinstance(self, other.__class__): @@ -67,6 +67,7 @@ class SubscriptionModel(db.Model): 'operationalPolicyName': self.operational_policy_name, 'controlLoopName': self.control_loop_name, 'nfFilter': self.network_filter.serialize(), + 'nfs': [nf.nf_name for nf in self.nfs], 'measurementGroups': [mg.serialize() for mg in self.measurement_groups]}} @@ -138,8 +139,8 @@ class NfSubRelationalModel(db.Model): self.nf_sub_status = nf_sub_status def __repr__(self): - return f'subscription_name: {self.subscription_name}, ' \ - f'nf_name: {self.nf_name}, nf_sub_status: {self.nf_sub_status}' + return (f'subscription_name: {self.subscription_name}, ' + f'nf_name: {self.nf_name}, nf_sub_status: {self.nf_sub_status}') def serialize(self): return {'subscription_name': self.subscription_name, 'nf_name': self.nf_name, @@ -182,9 +183,9 @@ class NetworkFunctionFilterModel(db.Model): self.model_names = model_names def __repr__(self): - return f'subscription_name: {self.subscription_name}, ' \ - f'nf_names: {self.nf_names}, model_invariant_ids: {self.model_invariant_ids}' \ - f'model_version_ids: {self.model_version_ids}, model_names: {self.model_names}' + return (f'subscription_name: {self.subscription_name}, ' + f'nf_names: {self.nf_names}, model_invariant_ids: {self.model_invariant_ids}, ' + f'model_version_ids: {self.model_version_ids}, model_names: {self.model_names}') def serialize(self): return {'nfNames': convert_db_string_to_list(self.nf_names), @@ -219,13 +220,13 @@ class MeasurementGroupModel(db.Model): self.managed_object_dns_basic = managed_object_dns_basic def __repr__(self): - return f'subscription_name: {self.subscription_name}, ' \ - f'measurement_group_name: {self.measurement_group_name},' \ - f'administrative_state: {self.administrative_state},' \ - f'file_based_gp: {self.file_based_gp},' \ - f'file_location: {self.file_location},' \ - f'measurement_type: {self.measurement_type}' \ - f'managed_object_dns_basic: {self.managed_object_dns_basic}' + return (f'subscription_name: {self.subscription_name}, ' + f'measurement_group_name: {self.measurement_group_name}, ' + f'administrative_state: {self.administrative_state}, ' + f'file_based_gp: {self.file_based_gp}, ' + f'file_location: {self.file_location}, ' + f'measurement_type: {self.measurement_type}, ' + f'managed_object_dns_basic: {self.managed_object_dns_basic}') def serialize(self): return {'measurementGroup': {'measurementGroupName': self.measurement_group_name, @@ -235,6 +236,28 @@ class MeasurementGroupModel(db.Model): 'measurementTypes': self.measurement_type, 'managedObjectDNsBasic': self.managed_object_dns_basic}} + def meas_group_with_nfs(self): + """ + Generates the dictionary of subscription name, measurement group name, administrative state + and network functions + + Returns: + dict: of subscription name, measurement group name, administrative state + and network functions + """ + meas_group_nfs = db.session.query(NfMeasureGroupRelationalModel).filter( + NfMeasureGroupRelationalModel.measurement_grp_name == self.measurement_group_name).all() + db.session.remove() + return {'subscriptionName': self.subscription_name, + 'measurementGroupName': self.measurement_group_name, + 'administrativeState': self.administrative_state, + 'fileBasedGP': self.file_based_gp, + 'fileLocation': self.file_location, + 'measurementTypes': self.measurement_type, + 'managedObjectDNsBasic': self.managed_object_dns_basic, + 'networkFunctions': + [meas_group_nf.serialize_meas_group_nfs() for meas_group_nf in meas_group_nfs]} + class NfMeasureGroupRelationalModel(db.Model): __tablename__ = 'nf_to_measure_grp_rel' @@ -262,8 +285,28 @@ class NfMeasureGroupRelationalModel(db.Model): self.retry_count = retry_count def __repr__(self): - return f'measurement_grp_name: {self.measurement_grp_name}, ' \ - f'nf_name: {self.nf_name}, nf_measure_grp_status: {self.nf_measure_grp_status}' + return (f'measurement_grp_name: {self.measurement_grp_name}, ' + f'nf_name: {self.nf_name}, nf_measure_grp_status: {self.nf_measure_grp_status}') + + def serialize_meas_group_nfs(self): + """ + Generates the dictionary of all the network function properties + + Returns: + dict: of network function properties + """ + nf = db.session.query(NetworkFunctionModel).filter( + NetworkFunctionModel.nf_name == self.nf_name).one_or_none() + db.session.remove() + return {'nfName': self.nf_name, + 'ipv4Address': nf.ipv4_address, + 'ipv6Address': nf.ipv6_address, + 'nfMgStatus': self.nf_measure_grp_status, + 'modelInvariantId': nf.model_invariant_id, + 'modelVersionId': nf.model_version_id, + 'modelName': nf.model_name, + 'sdncModelName': nf.sdnc_model_name, + 'sdncModelVersion': nf.sdnc_model_version} def convert_db_string_to_list(db_string): diff --git a/components/pm-subscription-handler/pmsh_service/mod/api/pmsh_swagger.yml b/components/pm-subscription-handler/pmsh_service/mod/api/pmsh_swagger.yml index f27fb7ab..4dd1cc70 100644 --- a/components/pm-subscription-handler/pmsh_service/mod/api/pmsh_swagger.yml +++ b/components/pm-subscription-handler/pmsh_service/mod/api/pmsh_swagger.yml @@ -72,9 +72,24 @@ paths: 400: description: Invalid input + get: + description: Get all the subscriptions from PMSH. + operationId: mod.api.controller.get_subscriptions + tags: + - "Subscription" + responses: + 200: + description: OK; Array of subscriptions are returned else empty if not found + schema: + type: array + items: + $ref: "#/definitions/subscription" + 500: + description: Exception occurred while querying database + /subscription/{subscription_name}: get: - description: Get the Subscription from ONAP specified by Name + description: Get the Subscription from PMSH specified by Name operationId: mod.api.controller.get_subscription_by_name tags: - "Subscription" @@ -92,7 +107,35 @@ paths: 404: description: Subscription with specified name not found 500: - description: Exception occurs while querying database + description: Exception occurred while querying database + + /subscription/{subscription_name}/measurementGroups/{measurement_group_name}: + get: + description: Get the measurement group and associated network functions + from PMSH by using sub name and meas group name + operationId: mod.api.controller.get_meas_group_with_nfs + tags: + - "measurement group" + parameters: + - name : subscription_name + in: path + required: true + description: Name of the subscription + type: string + - name: measurement_group_name + in: path + required: true + description: Name of the measurement group name + type: string + responses: + 200: + description: OK; Received requested measurement group with associated NF's + schema: + $ref : "#/definitions/measGroupWithNFs" + 404: + description: Measurement group with specified name not found + 500: + description: Exception occurred while querying database definitions: subscription: @@ -200,3 +243,60 @@ definitions: type: string required: - DN + + measGroupWithNFs: + type: object + properties: + subscriptionName: + type: string + measurementGroupName: + type: string + administrativeState: + type: string + enum: [ LOCKED, UNLOCKED ] + fileBasedGP: + type: integer + fileLocation: + type: string + measurementTypes: + type: array + minItems: 1 + items: + $ref: "#/definitions/measurementType" + managedObjectDNsBasic: + type: array + minItems: 1 + items: + $ref: "#/definitions/managedObjectDNs" + network_functions: + type: array + items: + type: object + properties: + nfName: + type: string + description: Name of the Network Function + ipv4Address: + type: string + description: Address of the IPV4 + ipv6Address: + type: string + description: Address of the IPV6 + nfMgStatus: + type: string + description: status of network function for one meas group + modelInvariantId: + type: string + description: ID of the model invariant + modelVersionId: + type: string + description: ID of the model version + modelName: + type: string + description: Name of the model + sdncModelName: + type: string + description: Name of the sdnc model + sdncModelVersion: + type: string + description: Version of the sdnc model diff --git a/components/pm-subscription-handler/pmsh_service/mod/api/services/measurement_group_service.py b/components/pm-subscription-handler/pmsh_service/mod/api/services/measurement_group_service.py index 733d803e..692efe27 100644 --- a/components/pm-subscription-handler/pmsh_service/mod/api/services/measurement_group_service.py +++ b/components/pm-subscription-handler/pmsh_service/mod/api/services/measurement_group_service.py @@ -18,8 +18,8 @@ from mod.api.db_models import MeasurementGroupModel, NfMeasureGroupRelationalModel from mod import db, logger -from mod.subscription import SubNfState from mod.api.services import nf_service +from mod.network_function import NetworkFunction from mod.pmsh_config import MRTopic, AppConfig @@ -47,18 +47,19 @@ def save_measurement_group(measurement_group, subscription_name): return new_measurement_group -def apply_nf_to_measgroup(nf_name, measurement_group_name): +def apply_nf_status_to_measurement_group(nf_name, measurement_group_name, status): """ Associate and saves the measurement group with Network function Args: nf_name (string): Network function name. measurement_group_name (string): Measurement group name + status (string): nf status to apply on measurement group """ new_nf_measure_grp_rel = NfMeasureGroupRelationalModel( measurement_grp_name=measurement_group_name, nf_name=nf_name, - nf_measure_grp_status=SubNfState.PENDING_CREATE.value + nf_measure_grp_status=status ) db.session.add(new_nf_measure_grp_rel) @@ -86,3 +87,68 @@ def publish_measurement_group(sub_model, measurement_group, nf): } logger.debug(f'Event Body: {event_body}') AppConfig.get_instance().publish_to_topic(MRTopic.POLICY_PM_PUBLISHER.value, event_body) + + +def update_measurement_group_nf_status(measurement_group_name, status, nf_name): + """ Updates the status of a measurement grp for a particular nf + + Args: + measurement_group_name (string): Measurement group name + nf_name (string): The network function name + status (string): status of the network function for measurement group + """ + try: + logger.info(f'Performing update for measurement group name: {measurement_group_name},' + f' network function name: {nf_name} on status: {status}') + NfMeasureGroupRelationalModel.query.filter( + NfMeasureGroupRelationalModel.measurement_grp_name == measurement_group_name, + NfMeasureGroupRelationalModel.nf_name == nf_name). \ + update({NfMeasureGroupRelationalModel.nf_measure_grp_status: status}, + synchronize_session='evaluate') + db.session.commit() + except Exception as e: + logger.error(f'Failed to update nf: {nf_name} for measurement group: ' + f'{measurement_group_name} due to: {e}') + + +def delete_nf_to_measurement_group(nf_name, measurement_group_name, status): + """ Deletes a particular nf related to a measurement group name and + if no more relations of nf exist to measurement group then delete nf from PMSH + + Args: + nf_name (string): The network function name + measurement_group_name (string): Measurement group name + status (string): status of the network function for measurement group + """ + try: + logger.info(f'Performing delete for measurement group name: {measurement_group_name},' + f' network function name: {nf_name} on status: {status}') + nf_measurement_group_rel = NfMeasureGroupRelationalModel.query.filter( + NfMeasureGroupRelationalModel.measurement_grp_name == measurement_group_name, + NfMeasureGroupRelationalModel.nf_name == nf_name).one_or_none() + db.session.delete(nf_measurement_group_rel) + db.session.commit() + nf_relations = NfMeasureGroupRelationalModel.query.filter( + NfMeasureGroupRelationalModel.nf_name == nf_name).all() + if not nf_relations: + NetworkFunction.delete(nf_name=nf_name) + except Exception as e: + logger.error(f'Failed to delete nf: {nf_name} for measurement group: ' + f'{measurement_group_name} due to: {e}') + + +def query_meas_group_by_name(subscription_name, measurement_group_name): + """ + Retrieves the measurement group by using sub name and measurement group name + + Args: + subscription_name (String): Name of the subscription. + measurement_group_name (String): Name of the measurement group + + Returns: + MeasurementGroupModel: queried measurement group (or) None + """ + meas_group = db.session.query(MeasurementGroupModel).filter( + MeasurementGroupModel.subscription_name == subscription_name, + MeasurementGroupModel.measurement_group_name == measurement_group_name).one_or_none() + return meas_group 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 c41bb18b..fc27f992 100644 --- a/components/pm-subscription-handler/pmsh_service/mod/api/services/subscription_service.py +++ b/components/pm-subscription-handler/pmsh_service/mod/api/services/subscription_service.py @@ -21,7 +21,7 @@ from mod.api.db_models import SubscriptionModel, NfSubRelationalModel, \ NetworkFunctionFilterModel, NetworkFunctionModel from mod.api.services import measurement_group_service, nf_service from mod.api.custom_exception import InvalidDataException, DuplicateDataException -from mod.subscription import AdministrativeState +from mod.subscription import AdministrativeState, SubNfState from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import joinedload @@ -129,7 +129,7 @@ def apply_measurement_grp_to_nfs(filtered_nfs, measurement_groups): Saves measurement groups against nfs with status as PENDING_CREATE Args: - filtered_nfs (list[NetworkFunction])): list of filtered network functions + filtered_nfs (list[NetworkFunction]): list of filtered network functions measurement_groups (list[MeasurementGroupModel]): list of measurement group Returns: @@ -143,8 +143,9 @@ def apply_measurement_grp_to_nfs(filtered_nfs, measurement_groups): for nf in filtered_nfs: logger.info(f'Saving measurement group to nf name, measure_grp_name: {nf.nf_name},' f'{measurement_group.measurement_group_name}') - measurement_group_service.apply_nf_to_measgroup( - nf.nf_name, measurement_group.measurement_group_name) + measurement_group_service.apply_nf_status_to_measurement_group( + nf.nf_name, measurement_group.measurement_group_name, + SubNfState.PENDING_CREATE.value) else: logger.info(f'No nfs added as measure_grp_name: ' f'{measurement_group.measurement_group_name} is LOCKED') @@ -292,9 +293,9 @@ def save_nf_filter(nf_filter, subscription_name): db.session.add(new_filter) -def get_subscription_by_name(subscription_name): +def query_subscription_by_name(subscription_name): """ - Retrieves the subscription information by name + Queries the db for existing subscription by name Args: subscription_name (String): Name of the Subscription @@ -305,7 +306,41 @@ def get_subscription_by_name(subscription_name): logger.info(f'Attempting to fetch subscription by name: {subscription_name}') subscription_model = db.session.query(SubscriptionModel) \ .options(joinedload(SubscriptionModel.network_filter), - joinedload(SubscriptionModel.measurement_groups)) \ + joinedload(SubscriptionModel.measurement_groups), + joinedload(SubscriptionModel.nfs)) \ .filter_by(subscription_name=subscription_name).first() db.session.remove() return subscription_model + + +def query_all_subscriptions(): + """ + Queries the db for all existing subscriptions defined in PMSH + + Returns + list (SubscriptionModel): of all subscriptions else None + """ + logger.info('Attempting to fetch all the subscriptions') + subscriptions = db.session.query(SubscriptionModel) \ + .options(joinedload(SubscriptionModel.network_filter), + joinedload(SubscriptionModel.measurement_groups)) \ + .all() + db.session.remove() + return subscriptions + + +def get_subscriptions_list(): + """ Converts all subscriptions to JSON and appends to list + + Returns + list: dict of all subscriptions else empty + """ + subscriptions = query_all_subscriptions() + subscriptions_list = [] + if subscriptions is not None: + logger.info('Queried all the subscriptions was successful') + for subscription in subscriptions: + if (subscription.network_filter is not None) and \ + (len(subscription.measurement_groups) != 0): + subscriptions_list.append(subscription.serialize()) + return subscriptions_list 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 index 09c97047..1bc58081 100644 --- a/components/pm-subscription-handler/pmsh_service/mod/policy_response_handler.py +++ b/components/pm-subscription-handler/pmsh_service/mod/policy_response_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,33 +15,31 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== - import json - +from mod.pmsh_config import MRTopic, AppConfig from mod import logger -from mod.network_function import NetworkFunction -from mod.subscription import Subscription, AdministrativeState, subscription_nf_states +from mod.subscription import AdministrativeState, subscription_nf_states +from mod.api.db_models import MeasurementGroupModel +from mod.api.services import measurement_group_service policy_response_handle_functions = { AdministrativeState.LOCKED.value: { - 'success': NetworkFunction.delete, - 'failed': Subscription.update_sub_nf_status + 'success': measurement_group_service.delete_nf_to_measurement_group, + 'failed': measurement_group_service.update_measurement_group_nf_status }, AdministrativeState.UNLOCKED.value: { - 'success': Subscription.update_sub_nf_status, - 'failed': Subscription.update_sub_nf_status + 'success': measurement_group_service.update_measurement_group_nf_status, + 'failed': measurement_group_service.update_measurement_group_nf_status }, AdministrativeState.LOCKING.value: { - 'success': NetworkFunction.delete, - 'failed': Subscription.update_sub_nf_status + 'success': measurement_group_service.delete_nf_to_measurement_group, + 'failed': measurement_group_service.update_measurement_group_nf_status } } class PolicyResponseHandler: - def __init__(self, mr_sub, app_conf, app): - self.mr_sub = mr_sub - self.app_conf = app_conf + def __init__(self, app): self.app = app def poll_policy_topic(self): @@ -50,38 +48,49 @@ class PolicyResponseHandler: relevant subscription and then handles the response """ self.app.app_context().push() - administrative_state = self.app_conf.subscription.administrativeState logger.info('Polling MR for XNF activation/deactivation policy response events.') try: - response_data = self.mr_sub.get_from_topic('dcae_pmsh_policy_cl_input') + response_data = AppConfig.get_instance(). \ + get_from_topic(MRTopic.POLICY_PM_SUBSCRIBER.value, 'dcae_pmsh_policy_cl_input') for data in response_data: data = json.loads(data) - if data['status']['subscriptionName'] \ - == self.app_conf.subscription.subscriptionName: - nf_name = data['status']['nfName'] - response_message = data['status']['message'] - self._handle_response(self.app_conf.subscription.subscriptionName, - administrative_state, nf_name, response_message) + measurement_group_name = data['status']['measurementGroupName'] + subscription_name = data['status']['subscriptionName'] + measurement_group = (MeasurementGroupModel.query.filter( + MeasurementGroupModel.measurement_group_name == measurement_group_name, + subscription_name == MeasurementGroupModel + .subscription_name).one_or_none()) + nf_name = data['status']['nfName'] + response_message = data['status']['message'] + if measurement_group: + self._handle_response(measurement_group_name, + measurement_group.administrative_state, + nf_name, response_message) + else: + logger.info(f'Polled MR response provides missing measurement ' + f'group name : {measurement_group_name}') except Exception as err: logger.error(f'Error trying to poll policy response topic on MR: {err}', exc_info=True) @staticmethod - def _handle_response(subscription_name, administrative_state, nf_name, response_message): + def _handle_response(measurement_group_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) + measurement_group_name (string): The measurement group name + administrative_state (string): The administrative state of the measurement group + nf_name (string): The network function name + response_message (string): The message in the response + regarding the state (success|failed) """ - logger.info(f'Response from MR: Sub: {subscription_name} for ' + logger.info(f'Response from MR: measurement group name: {measurement_group_name} for ' f'NF: {nf_name} received, updating the DB') try: - sub_nf_status = subscription_nf_states[administrative_state][response_message].value + nf_measure_grp_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) + measurement_group_name=measurement_group_name, status=nf_measure_grp_status, + nf_name=nf_name) except Exception as err: logger.error(f'Error changing nf_sub status in the DB: {err}') raise diff --git a/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py b/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py index 29f9121d..5fbb9a6c 100644 --- a/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py +++ b/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py @@ -18,9 +18,7 @@ from jsonschema import ValidationError from mod import logger, aai_client -from mod.aai_event_handler import process_aai_events from mod.network_function import NetworkFunctionFilter -from mod.pmsh_utils import PeriodicTask from mod.subscription import AdministrativeState @@ -73,7 +71,6 @@ class SubscriptionHandler: def _activate(self, new_administrative_state): if not self.app_conf.nf_filter: self.app_conf.nf_filter = NetworkFunctionFilter(**self.app_conf.subscription.nfFilter) - self._start_aai_event_thread() self.app_conf.subscription.update_sub_params(new_administrative_state, self.app_conf.subscription.fileBasedGP, self.app_conf.subscription.fileLocation, @@ -91,15 +88,6 @@ class SubscriptionHandler: self.app_conf.subscription.delete_subscription_from_nfs(nfs, self.mr_pub) self.app_conf.subscription.update_subscription_status() - def _start_aai_event_thread(self): - logger.info('Starting polling for NF info on AAI-EVENT topic on DMaaP MR.') - self.aai_event_thread = PeriodicTask(20, process_aai_events, args=(self.aai_sub, - self.mr_pub, - self.app, - self.app_conf)) - self.aai_event_thread.name = 'aai_event_thread' - self.aai_event_thread.start() - def stop_aai_event_thread(self): if self.aai_event_thread is not None: self.aai_event_thread.cancel() diff --git a/components/pm-subscription-handler/pmsh_service/pmsh_service_main.py b/components/pm-subscription-handler/pmsh_service/pmsh_service_main.py index 0b6544b5..1af01cf1 100755 --- a/components/pm-subscription-handler/pmsh_service/pmsh_service_main.py +++ b/components/pm-subscription-handler/pmsh_service/pmsh_service_main.py @@ -17,7 +17,7 @@ # ============LICENSE_END===================================================== import sys from signal import signal, SIGTERM - +from mod.aai_event_handler import AAIEventHandler from mod import db, create_app, launch_api_server, logger from mod.exit_handler import ExitHandler from mod.pmsh_config import AppConfig as NewAppConfig @@ -35,13 +35,12 @@ def main(): app_conf = AppConfig() pmsh_app_conf = NewAppConfig() policy_mr_pub = app_conf.get_mr_pub('policy_pm_publisher') - policy_mr_sub = app_conf.get_mr_sub('policy_pm_subscriber') aai_event_mr_sub = app_conf.get_mr_sub('aai_subscriber') except Exception as e: logger.error(f'Failed to get config and create application: {e}', exc_info=True) sys.exit(e) - policy_response_handler = PolicyResponseHandler(policy_mr_sub, app_conf, app) + policy_response_handler = PolicyResponseHandler(app) policy_response_handler_thread = PeriodicTask(25, policy_response_handler.poll_policy_topic) policy_response_handler_thread.name = 'policy_event_thread' logger.info('Start polling PMSH_CL_INPUT topic on DMaaP MR.') @@ -52,8 +51,13 @@ def main(): subscription_handler_thread.name = 'sub_handler_thread' subscription_handler_thread.start() - periodic_tasks = [subscription_handler_thread, policy_response_handler_thread] + aai_event_handler = AAIEventHandler(app) + aai_event_handler_thread = PeriodicTask(20, aai_event_handler.execute) + aai_event_handler_thread.name = 'aai_event_thread' + aai_event_handler_thread.start() + periodic_tasks = [subscription_handler_thread, policy_response_handler_thread, + aai_event_handler_thread] signal(SIGTERM, ExitHandler(periodic_tasks=periodic_tasks, app_conf=app_conf, subscription_handler=subscription_handler)) launch_api_server(pmsh_app_conf) diff --git a/components/pm-subscription-handler/tests/base_setup.py b/components/pm-subscription-handler/tests/base_setup.py index 4328f59c..560eaeb8 100755 --- a/components/pm-subscription-handler/tests/base_setup.py +++ b/components/pm-subscription-handler/tests/base_setup.py @@ -22,18 +22,37 @@ from unittest import TestCase from unittest.mock import patch, MagicMock from mod import create_app, db -from mod.api.db_models import NetworkFunctionFilterModel, MeasurementGroupModel, SubscriptionModel +from mod.api.db_models import NetworkFunctionFilterModel, MeasurementGroupModel, \ + SubscriptionModel, NetworkFunctionModel, NfSubRelationalModel from mod.network_function import NetworkFunctionFilter from mod.pmsh_utils import AppConfig from mod.pmsh_config import AppConfig as NewAppConfig def get_pmsh_config(file_path='data/cbs_data_1.json'): + """ + Gets PMSH config from the JSON file + + Args: + file_path (String): Name of the file with path + + Returns + dict: Dictionary representation of the the service configuration + """ with open(os.path.join(os.path.dirname(__file__), file_path), 'r') as data: return json.load(data) -def subscription_data(subscription_name): +def create_subscription_data(subscription_name): + """ + Creates subscription model object + + Args: + subscription_name (String): Name of the Subscription + + Returns + SubscriptionModel: single subscription model object + """ nf_filter = NetworkFunctionFilterModel(subscription_name, '{^pnf.*,^vnf.*}', '{}', '{}', '{}') mg_first = MeasurementGroupModel(subscription_name, 'MG1', 'UNLOCKED', 15, '/pm/pm.xml', @@ -48,9 +67,48 @@ def subscription_data(subscription_name): 'pmsh_control_loop_name', 'LOCKED') subscription_model.network_filter = nf_filter subscription_model.measurement_groups = mg_list + nf1 = NfSubRelationalModel(subscription_name, "pnf_101", "LOCKED") + nf2 = NfSubRelationalModel(subscription_name, "pnf_102", "LOCKED") + subscription_model.nfs = [nf1, nf2] return subscription_model +def create_multiple_subscription_data(subscription_names): + """ + Creates a list of subscription model objects + + Args: + subscription_names (List): Name of the Subscriptions + + Returns + list (SubscriptionModel): of subscription model objects + """ + subscriptions = [] + for subscription_name in subscription_names: + subscriptions.append(create_subscription_data(subscription_name)) + return subscriptions + + +def create_multiple_network_function_data(nf_name_list): + """ + Creates list of network function model objects + + Args: + nf_name_list (list): Network function names + + Returns + list: of network function model objects + """ + nf_list = [] + for nf_name in nf_name_list: + nf = NetworkFunctionModel(nf_name, '10.10.10.32', '2001:0db8:0:0:0:0:1428:57ab', + '687kj45-d396-4efb-af02-6b83499b12f8', + 'e80a6ae3-cafd-4d24-850d-e14c084a5ca9', + 'model_name', 'pm_control', '1.0.2') + nf_list.append(nf) + return nf_list + + class BaseClassSetup(TestCase): app = None app_context = None diff --git a/components/pm-subscription-handler/tests/data/mr_aai_events.json b/components/pm-subscription-handler/tests/data/mr_aai_events.json index 19758982..42639d9f 100755 --- a/components/pm-subscription-handler/tests/data/mr_aai_events.json +++ b/components/pm-subscription-handler/tests/data/mr_aai_events.json @@ -2,7 +2,8 @@ "mr_response": [
"{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"pnf\",\"top-entity-type\":\"pnf\",\"entity-link\":\"/aai/v16/network/pnfs/pnf/pnf_newly_discovered\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"UPDATE\",\"sequence-number\":\"0\",\"id\":\"db09e090-196e-4f84-9645-e449b1cd3640\",\"source-name\":\"dcae-curl\",\"version\":\"v16\",\"timestamp\":\"20200203-15:14:08:807\"},\"entity\":{\"ipaddress-v4-oam\":\"10.10.10.37\",\"nf-role\":\"gNB\",\"equip-type\":\"val8\",\"relationship-list\":{\"relationship\":[{\"related-to\":\"service-instance\",\"relationship-data\":[{\"relationship-value\":\"Demonstration\",\"relationship-key\":\"customer.global-customer-id\"},{\"relationship-value\":\"vCPE\",\"relationship-key\":\"service-subscription.service-type\"},{\"relationship-value\":\"2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-key\":\"service-instance.service-instance-id\"}],\"related-link\":\"/aai/v16/business/customers/customer/Demonstration/service-subscriptions/service-subscription/vCPE/service-instances/service-instance/2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-label\":\"org.onap.relationships.inventory.ComposedOf\",\"related-to-property\":[{\"property-key\":\"service-instance.service-instance-name\",\"property-value\":\"Svc6_1\"}]}]},\"equip-vendor\":\"Ericsson\",\"serial-number\":\"6061ZW3\",\"ipaddress-v6-oam\":\"2001:0db8:0:0:0:0:1428:57ab\",\"equip-model\":\"val6\",\"in-maint\":false,\"resource-version\":\"1578668956804\",\"sw-version\":\"val7\",\"pnf-id\":\"eabcfaf7-b7f3-45fb-94e7-e6112fb3e8b8\",\"pnf-name\":\"pnf_newly_discovered\",\"model-invariant-id\":\"7129e420-d396-4efb-af02-6b83499b12f8\",\"model-version-id\":\"e80a6ae3-cafd-4d24-850d-e14c084a5ca9\",\"orchestration-status\":\"Active\"}}",
"{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"pnf\",\"top-entity-type\":\"pnf\",\"entity-link\":\"/aai/v16/network/pnfs/pnf/pnf_newly_discovered\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"UPDATE\",\"sequence-number\":\"0\",\"id\":\"db09e090-196e-4f84-9645-e449b1cd3640\",\"source-name\":\"dcae-curl\",\"version\":\"v16\",\"timestamp\":\"20200203-15:14:08:807\"},\"entity\":{\"orchestration-status\":\"Active\",\"ipaddress-v4-oam\":\"10.10.10.37\",\"nf-role\":\"gNB\",\"equip-type\":\"val8\",\"relationship-list\":{\"relationship\":[{\"related-to\":\"service-instance\",\"relationship-data\":[{\"relationship-value\":\"Demonstration\",\"relationship-key\":\"customer.global-customer-id\"},{\"relationship-value\":\"vCPE\",\"relationship-key\":\"service-subscription.service-type\"},{\"relationship-value\":\"2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-key\":\"service-instance.service-instance-id\"}],\"related-link\":\"/aai/v16/business/customers/customer/Demonstration/service-subscriptions/service-subscription/vCPE/service-instances/service-instance/2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-label\":\"org.onap.relationships.inventory.ComposedOf\",\"related-to-property\":[{\"property-key\":\"service-instance.service-instance-name\",\"property-value\":\"Svc6_1\"}]}]},\"equip-vendor\":\"Ericsson\",\"serial-number\":\"6061ZW3\",\"ipaddress-v6-oam\":\"2001:0db8:0:0:0:0:1428:57ab\",\"equip-model\":\"val6\",\"in-maint\":false,\"resource-version\":\"1578668956804\",\"sw-version\":\"val7\",\"pnf-id\":\"eabcfaf7-b7f3-45fb-94e7-e6112fb3e8b8\",\"pnf-name\":\"pnf_already_active\",\"model-invariant-id\":\"7129e420-d396-4efb-af02-6b83499b12f8\",\"model-version-id\":\"e80a6ae3-cafd-4d24-850d-e14c084a5ca9\",\"orchestration-status\":\"Active\"}}",
- "{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"pnf\",\"top-entity-type\":\"pnf\",\"entity-link\":\"/aai/v16/network/pnfs/pnf/pnf_newly_discovered\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"DELETE\",\"sequence-number\":\"0\",\"id\":\"db09e090-196e-4f84-9645-e449b1cd3640\",\"source-name\":\"dcae-curl\",\"version\":\"v16\",\"timestamp\":\"20200203-15:14:08:807\"},\"entity\":{\"ipaddress-v4-oam\":\"10.10.10.37\",\"nf-role\":\"gNB\",\"equip-type\":\"val8\",\"relationship-list\":{\"relationship\":[{\"related-to\":\"service-instance\",\"relationship-data\":[{\"relationship-value\":\"Demonstration\",\"relationship-key\":\"customer.global-customer-id\"},{\"relationship-value\":\"vCPE\",\"relationship-key\":\"service-subscription.service-type\"},{\"relationship-value\":\"2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-key\":\"service-instance.service-instance-id\"}],\"related-link\":\"/aai/v16/business/customers/customer/Demonstration/service-subscriptions/service-subscription/vCPE/service-instances/service-instance/2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-label\":\"org.onap.relationships.inventory.ComposedOf\",\"related-to-property\":[{\"property-key\":\"service-instance.service-instance-name\",\"property-value\":\"Svc6_1\"}]}]},\"equip-vendor\":\"Ericsson\",\"serial-number\":\"6061ZW3\",\"ipaddress-v6-oam\":\"2001:0db8:0:0:0:0:1428:57ab\",\"equip-model\":\"val6\",\"in-maint\":false,\"resource-version\":\"1578668956804\",\"sw-version\":\"val7\",\"pnf-id\":\"eabcfaf7-b7f3-45fb-94e7-e6112fb3e8b8\",\"pnf-name\":\"pnf_to_be_deleted\",\"model-invariant-id\":\"7129e420-d396-4efb-af02-6b83499b12f8\",\"model-version-id\":\"e80a6ae3-cafd-4d24-850d-e14c084a5ca9\",\"orchestration-status\":\"Active\"}}",
+ "{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"pnf\",\"top-entity-type\":\"pnf\",\"entity-link\":\"/aai/v16/network/pnfs/pnf/pnf_newly_discovered\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"DELETE\",\"sequence-number\":\"0\",\"id\":\"db09e090-196e-4f84-9645-e449b1cd3640\",\"source-name\":\"dcae-curl\",\"version\":\"v16\",\"timestamp\":\"20200203-15:14:08:807\"},\"entity\":{\"ipaddress-v4-oam\":\"10.10.10.37\",\"nf-role\":\"gNB\",\"equip-type\":\"val8\",\"relationship-list\":{\"relationship\":[{\"related-to\":\"service-instance\",\"relationship-data\":[{\"relationship-value\":\"Demonstration\",\"relationship-key\":\"customer.global-customer-id\"},{\"relationship-value\":\"vCPE\",\"relationship-key\":\"service-subscription.service-type\"},{\"relationship-value\":\"2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-key\":\"service-instance.service-instance-id\"}],\"related-link\":\"/aai/v16/business/customers/customer/Demonstration/service-subscriptions/service-subscription/vCPE/service-instances/service-instance/2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-label\":\"org.onap.relationships.inventory.ComposedOf\",\"related-to-property\":[{\"property-key\":\"service-instance.service-instance-name\",\"property-value\":\"Svc6_1\"}]}]},\"equip-vendor\":\"Ericsson\",\"serial-number\":\"6061ZW3\",\"ipaddress-v6-oam\":\"2001:0db8:0:0:0:0:1428:57ab\",\"equip-model\":\"val6\",\"in-maint\":false,\"resource-version\":\"1578668956804\",\"sw-version\":\"val7\",\"pnf-id\":\"eabcfaf7-b7f3-45fb-94e7-e6112fb3e8b8\",\"pnf-name\":\"pnf_to_be_deleted1\",\"model-invariant-id\":\"7129e420-d396-4efb-af02-6b83499b12f8\",\"model-version-id\":\"e80a6ae3-cafd-4d24-850d-e14c084a5ca9\",\"orchestration-status\":\"Active\"}}",
+ "{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"pnf\",\"top-entity-type\":\"pnf\",\"entity-link\":\"/aai/v16/network/pnfs/pnf/pnf_newly_discovered\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"DELETE\",\"sequence-number\":\"0\",\"id\":\"db09e090-196e-4f84-9645-e449b1cd3641\",\"source-name\":\"dcae-curl\",\"version\":\"v16\",\"timestamp\":\"20200203-15:14:08:807\"},\"entity\":{\"ipaddress-v4-oam\":\"10.10.10.38\",\"nf-role\":\"gNB\",\"equip-type\":\"val8\",\"relationship-list\":{\"relationship\":[{\"related-to\":\"service-instance\",\"relationship-data\":[{\"relationship-value\":\"Demonstration\",\"relationship-key\":\"customer.global-customer-id\"},{\"relationship-value\":\"vCPE\",\"relationship-key\":\"service-subscription.service-type\"},{\"relationship-value\":\"2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-key\":\"service-instance.service-instance-id\"}],\"related-link\":\"/aai/v16/business/customers/customer/Demonstration/service-subscriptions/service-subscription/vCPE/service-instances/service-instance/2c03b2a8-e31a-4749-9e99-3089ab441400\",\"relationship-label\":\"org.onap.relationships.inventory.ComposedOf\",\"related-to-property\":[{\"property-key\":\"service-instance.service-instance-name\",\"property-value\":\"Svc6_1\"}]}]},\"equip-vendor\":\"Ericsson\",\"serial-number\":\"6061ZW3\",\"ipaddress-v6-oam\":\"2001:0db8:0:0:0:0:1428:57ab\",\"equip-model\":\"val6\",\"in-maint\":false,\"resource-version\":\"1578668956804\",\"sw-version\":\"val7\",\"pnf-id\":\"eabcfaf7-b7f3-45fb-94e7-e6112fb3e8b8\",\"pnf-name\":\"pnf_to_be_deleted2\",\"model-invariant-id\":\"7129e420-d396-4efb-af02-6b83499b12f8\",\"model-version-id\":\"e80a6ae3-cafd-4d24-850d-e14c084a5ca9\",\"orchestration-status\":\"Active\"}}",
"{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"model-ver\",\"top-entity-type\":\"model\",\"entity-link\":\"/aai/v19/service-design-and-creation/models/model/60cd7bbf-0a6b-43ce-a5af-07cbf168ecdc/model-vers/model-ver/5485c7ac-825f-414d-aba4-e24147107d0b\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"UPDATE\",\"sequence-number\":\"0\",\"id\":\"e16cb0f9-3617-4a14-9c81-f7ee4cdf9df7\",\"source-name\":\"UNKNOWN\",\"version\":\"v19\",\"timestamp\":\"20200701-12:06:12:135\"},\"entity\":{\"model-type\":\"service\",\"model-vers\":{\"model-ver\":[{\"model-version-id\":\"5485c7ac-825f-414d-aba4-e24147107d0b\",\"resource-version\":\"1593605171956\",\"model-description\":\"catalog service description\",\"model-name\":\"Demo_pNF_WCcM9Z2VNE3eGfLIKulP\",\"model-elements\":{\"model-element\":[{\"relationship-list\":{\"relationship\":[{\"related-to\":\"model-ver\",\"relationship-data\":[{\"relationship-value\":\"82194af1-3c2c-485a-8f44-420e22a9eaa4\",\"relationship-key\":\"model.model-invariant-id\"},{\"relationship-value\":\"46b92144-923a-4d20-b85a-3cbd847668a9\",\"relationship-key\":\"model-ver.model-version-id\"}],\"related-link\":\"/aai/v19/service-design-and-creation/models/model/82194af1-3c2c-485a-8f44-420e22a9eaa4/model-vers/model-ver/46b92144-923a-4d20-b85a-3cbd847668a9\",\"relationship-label\":\"org.onap.relationships.inventory.IsA\",\"related-to-property\":[{\"property-key\":\"model-ver.model-name\",\"property-value\":\"service-instance\"}]}]},\"resource-version\":\"1593605012006\",\"model-elements\":{\"model-element\":[{\"relationship-list\":{\"relationship\":[{\"related-to\":\"model-ver\",\"relationship-data\":[{\"relationship-value\":\"c8fb1064-f38a-4a13-90a6-ec60289879ac\",\"relationship-key\":\"model.model-invariant-id\"},{\"relationship-value\":\"94c051c3-4484-425e-a107-6914816321a6\",\"relationship-key\":\"model-ver.model-version-id\"}],\"related-link\":\"/aai/v19/service-design-and-creation/models/model/c8fb1064-f38a-4a13-90a6-ec60289879ac/model-vers/model-ver/94c051c3-4484-425e-a107-6914816321a6\",\"relationship-label\":\"org.onap.relationships.inventory.IsA\",\"related-to-property\":[{\"property-key\":\"model-ver.model-name\",\"property-value\":\"pNF 77be3d89-b735\"}]}]},\"resource-version\":\"1593605012006\",\"new-data-del-flag\":\"T\",\"cardinality\":\"unbounded\",\"model-element-uuid\":\"44712769-981d-4970-9ea5-4c99b86f838e\"}]},\"new-data-del-flag\":\"T\",\"cardinality\":\"unbounded\",\"model-element-uuid\":\"4a601305-9e09-4152-98ad-b8c466d57813\"}]},\"model-version\":\"1.0\",\"distribution-status\":\"DISTRIBUTION_COMPLETE_OK\"}]},\"model-invariant-id\":\"60cd7bbf-0a6b-43ce-a5af-07cbf168ecdc\"}}",
"{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"customer\",\"top-entity-type\":\"customer\",\"entity-link\":\"/aai/v19/business/customers/customer/ETE_Customer_5638d7b4-a02d-4cc4-801e-77ee837d2855\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"CREATE\",\"sequence-number\":\"0\",\"id\":\"ef390383-3d08-4268-a013-d11c14efd716\",\"source-name\":\"robot-ete\",\"version\":\"v19\",\"timestamp\":\"20200701-12:06:50:388\"},\"entity\":{\"global-customer-id\":\"ETE_Customer_5638d7b4-a02d-4cc4-801e-77ee837d2855\",\"subscriber-type\":\"INFRA\",\"resource-version\":\"1593605210258\",\"subscriber-name\":\"ETE_Customer_5638d7b4-a02d-4cc4-801e-77ee837d2855\",\"service-subscriptions\":{\"service-subscription\":[{\"relationship-list\":{\"relationship\":[{\"related-to\":\"tenant\",\"relationship-data\":[{\"relationship-value\":\"CloudOwner\",\"relationship-key\":\"cloud-region.cloud-owner\"},{\"relationship-value\":\"RegionForPNF\",\"relationship-key\":\"cloud-region.cloud-region-id\"},{\"relationship-value\":\"dummy_tenant_id_for_pnf\",\"relationship-key\":\"tenant.tenant-id\"}],\"related-link\":\"/aai/v19/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner/RegionForPNF/tenants/tenant/dummy_tenant_id_for_pnf\",\"relationship-label\":\"org.onap.relationships.inventory.Uses\",\"related-to-property\":[{\"property-key\":\"tenant.tenant-name\",\"property-value\":\"dummy_tenant_for_pnf\"}]}]},\"resource-version\":\"1593605210258\",\"service-type\":\"pNF\"}]}}}"
]
diff --git a/components/pm-subscription-handler/tests/services/test_measurement_group_service.py b/components/pm-subscription-handler/tests/services/test_measurement_group_service.py index 46c4bac7..97353afe 100644 --- a/components/pm-subscription-handler/tests/services/test_measurement_group_service.py +++ b/components/pm-subscription-handler/tests/services/test_measurement_group_service.py @@ -23,9 +23,9 @@ from mod.network_function import NetworkFunction from mod.pmsh_config import AppConfig from mod import db from tests.base_setup import BaseClassSetup -from mod.api.services import measurement_group_service -from mod.api.db_models import MeasurementGroupModel, NfMeasureGroupRelationalModel,\ - SubscriptionModel +from mod.api.services import measurement_group_service, nf_service +from mod.api.db_models import MeasurementGroupModel, NfMeasureGroupRelationalModel, \ + SubscriptionModel, NetworkFunctionModel from mod.subscription import SubNfState @@ -99,17 +99,94 @@ class MeasurementGroupServiceTestCase(BaseClassSetup): MeasurementGroupModel.subscription_name == 'ExtraPM-All-gNB-R2B').one_or_none()) self.assertIsNotNone(measurement_grp) - def test_apply_nf_to_measgroup(self): - measurement_group_service.apply_nf_to_measgroup("pnf_test", "measure_grp_name") + def test_apply_nf_to_measurement_group_status(self): + measurement_group_service.apply_nf_status_to_measurement_group( + "pnf_test", "measure_grp_name", SubNfState.PENDING_CREATE.value) db.session.commit() measurement_grp_rel = (NfMeasureGroupRelationalModel.query.filter( NfMeasureGroupRelationalModel.measurement_grp_name == 'measure_grp_name', NfMeasureGroupRelationalModel.nf_name == 'pnf_test').one_or_none()) - db.session.commit() self.assertIsNotNone(measurement_grp_rel) self.assertEqual(measurement_grp_rel.nf_measure_grp_status, SubNfState.PENDING_CREATE.value) + def test_update_measurement_group_nf_status(self): + measurement_group_service.apply_nf_status_to_measurement_group( + "pnf_test", "measure_grp_name", SubNfState.PENDING_CREATE.value) + measurement_group_service.update_measurement_group_nf_status( + "measure_grp_name", SubNfState.CREATED.value, "pnf_test") + db.session.commit() + measurement_grp_rel = (NfMeasureGroupRelationalModel.query.filter( + NfMeasureGroupRelationalModel.measurement_grp_name == 'measure_grp_name', + NfMeasureGroupRelationalModel.nf_name == 'pnf_test').one_or_none()) + self.assertIsNotNone(measurement_grp_rel) + self.assertEqual(measurement_grp_rel.nf_measure_grp_status, + SubNfState.CREATED.value) + + def test_delete_nf_to_measurement_group_without_nf_delete(self): + nf = NetworkFunction(nf_name='pnf_test1') + nf_service.save_nf(nf) + db.session.commit() + measurement_group_service.apply_nf_status_to_measurement_group( + "pnf_test1", "measure_grp_name1", SubNfState.PENDING_CREATE.value) + measurement_group_service.apply_nf_status_to_measurement_group( + "pnf_test1", "measure_grp_name2", SubNfState.PENDING_CREATE.value) + measurement_group_service.delete_nf_to_measurement_group( + "pnf_test1", "measure_grp_name1", SubNfState.DELETED.value) + measurement_grp_rel = (NfMeasureGroupRelationalModel.query.filter( + NfMeasureGroupRelationalModel.measurement_grp_name == 'measure_grp_name1', + NfMeasureGroupRelationalModel.nf_name == 'pnf_test1').one_or_none()) + self.assertIsNone(measurement_grp_rel) + network_function = (NetworkFunctionModel.query.filter( + NetworkFunctionModel.nf_name == 'pnf_test1').one_or_none()) + self.assertIsNotNone(network_function) + + def test_delete_nf_to_measurement_group_with_nf_delete(self): + nf = NetworkFunction(nf_name='pnf_test2') + nf_service.save_nf(nf) + db.session.commit() + measurement_group_service.apply_nf_status_to_measurement_group( + "pnf_test2", "measure_grp_name2", SubNfState.PENDING_CREATE.value) + measurement_group_service.delete_nf_to_measurement_group( + "pnf_test2", "measure_grp_name2", SubNfState.DELETED.value) + measurement_grp_rel = (NfMeasureGroupRelationalModel.query.filter( + NfMeasureGroupRelationalModel.measurement_grp_name == 'measure_grp_name2', + NfMeasureGroupRelationalModel.nf_name == 'pnf_test2').one_or_none()) + self.assertIsNone(measurement_grp_rel) + network_function = (NetworkFunctionModel.query.filter( + NetworkFunctionModel.nf_name == 'pnf_test2').one_or_none()) + self.assertIsNone(network_function) + + @patch.object(NetworkFunction, 'delete') + @patch('mod.logger.error') + def test_delete_nf_to_measurement_group_failure(self, mock_logger, nf_delete_func): + nf = NetworkFunction(nf_name='pnf_test2') + nf_service.save_nf(nf) + db.session.commit() + measurement_group_service.apply_nf_status_to_measurement_group( + "pnf_test2", "measure_grp_name2", SubNfState.PENDING_CREATE.value) + nf_delete_func.side_effect = Exception('delete failed') + measurement_group_service.delete_nf_to_measurement_group( + "pnf_test2", "measure_grp_name2", SubNfState.DELETED.value) + measurement_grp_rel = (NfMeasureGroupRelationalModel.query.filter( + NfMeasureGroupRelationalModel.measurement_grp_name == 'measure_grp_name2', + NfMeasureGroupRelationalModel.nf_name == 'pnf_test2').one_or_none()) + self.assertIsNone(measurement_grp_rel) + network_function = (NetworkFunctionModel.query.filter( + NetworkFunctionModel.nf_name == 'pnf_test2').one_or_none()) + self.assertIsNotNone(network_function) + mock_logger.assert_called_with('Failed to delete nf: pnf_test2 for ' + 'measurement group: measure_grp_name2 due to: delete failed') + + @patch.object(db.session, 'commit') + @patch('mod.logger.error') + def test_update_nf_to_measurement_group_failure(self, mock_logger, db_commit_call): + db_commit_call.side_effect = Exception('update failed') + measurement_group_service.update_measurement_group_nf_status( + "measure_grp_name2", SubNfState.CREATE_FAILED.value, "pnf_test2") + mock_logger.assert_called_with('Failed to update nf: pnf_test2 for ' + 'measurement group: measure_grp_name2 due to: update failed') + def create_test_subs(self, new_sub_name, new_msrmt_grp_name): subscription = self.subscription_request.replace('ExtraPM-All-gNB-R2B', new_sub_name) subscription = subscription.replace('msrmt_grp_name', new_msrmt_grp_name) diff --git a/components/pm-subscription-handler/tests/services/test_subscription_service.py b/components/pm-subscription-handler/tests/services/test_subscription_service.py index a0e4d541..44fb4eff 100644 --- a/components/pm-subscription-handler/tests/services/test_subscription_service.py +++ b/components/pm-subscription-handler/tests/services/test_subscription_service.py @@ -29,6 +29,7 @@ from mod.api.custom_exception import DuplicateDataException, InvalidDataExceptio from mod.pmsh_config import AppConfig from tests.base_setup import BaseClassSetup from mod.api.services import subscription_service, nf_service, measurement_group_service +from tests.base_setup import create_multiple_subscription_data class SubscriptionServiceTestCase(BaseClassSetup): @@ -170,7 +171,7 @@ class SubscriptionServiceTestCase(BaseClassSetup): @patch.object(aai_client, '_get_all_aai_nf_data') @patch.object(aai_client, 'get_aai_model_data') - @patch.object(measurement_group_service, 'apply_nf_to_measgroup') + @patch.object(measurement_group_service, 'apply_nf_status_to_measurement_group') @patch.object(NetworkFunctionFilter, 'get_network_function_filter') def test_apply_measurement_grp_to_nfs(self, mock_filter_call, mock_apply_nf, mock_model_aai, mock_aai): @@ -357,3 +358,22 @@ class SubscriptionServiceTestCase(BaseClassSetup): db_string = '{}' db_array = convert_db_string_to_list(db_string) self.assertEqual(len(db_array), 0) + + @patch('mod.api.services.subscription_service.query_all_subscriptions', + MagicMock(return_value=create_multiple_subscription_data( + ['sub_demo_one', 'sub_demo_two']))) + def test_get_subscriptions_list(self): + subs = subscription_service.get_subscriptions_list() + self.assertEqual(subs[0]['subscription']['subscriptionName'], 'sub_demo_one') + self.assertEqual(subs[1]['subscription']['subscriptionName'], 'sub_demo_two') + self.assertEqual(subs[1]['subscription']['measurementGroups'][0]['measurementGroup'] + ['measurementGroupName'], 'MG1') + self.assertEqual(len(subs[1]['subscription']['measurementGroups']), 2) + self.assertEqual(len(subs), 2) + self.assertEqual(len(subs[0]['subscription']['nfs']), 2) + + @patch('mod.api.services.subscription_service.query_all_subscriptions', + MagicMock(return_value=[])) + def test_get_subscriptions_list_empty(self): + subs = subscription_service.get_subscriptions_list() + self.assertEqual(subs, []) diff --git a/components/pm-subscription-handler/tests/test_aai_event_handler.py b/components/pm-subscription-handler/tests/test_aai_event_handler.py index 0ae19429..d06b7728 100755 --- a/components/pm-subscription-handler/tests/test_aai_event_handler.py +++ b/components/pm-subscription-handler/tests/test_aai_event_handler.py @@ -1,5 +1,5 @@ # ============LICENSE_START=================================================== -# Copyright (C) 2020 Nordix Foundation. +# Copyright (C) 2020-2021 Nordix Foundation. # ============================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,27 +15,28 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== +import copy import json from os import path -from unittest.mock import patch, Mock - -from mod.aai_event_handler import process_aai_events +from unittest.mock import patch, MagicMock +from mod.aai_event_handler import AAIEventHandler +from mod.api.db_models import NetworkFunctionModel, NetworkFunctionFilterModel, \ + MeasurementGroupModel, SubscriptionModel +from mod.subscription import AdministrativeState from tests.base_setup import BaseClassSetup +from mod import db class AAIEventHandlerTest(BaseClassSetup): - @classmethod def setUpClass(cls): super().setUpClass() def setUp(self): super().setUp() + super().setUpAppConf() with open(path.join(path.dirname(__file__), 'data/mr_aai_events.json'), 'r') as data: self.mr_aai_events = json.load(data)["mr_response"] - self.mock_mr_sub = Mock(get_from_topic=Mock(return_value=self.mr_aai_events)) - self.mock_mr_pub = Mock() - self.mock_app = Mock() def tearDown(self): super().tearDown() @@ -44,12 +45,100 @@ class AAIEventHandlerTest(BaseClassSetup): def tearDownClass(cls): super().tearDownClass() - @patch('mod.network_function.NetworkFunction.set_nf_model_params') - @patch('mod.subscription.Subscription.create_subscription_on_nfs') + @patch('mod.pmsh_config.AppConfig.get_from_topic') @patch('mod.aai_event_handler.NetworkFunction.delete') - def test_process_aai_update_and_delete_events(self, mock_nf_delete, mock_activate_sub, - mock_set_sdnc_params): + def test_process_aai_delete_events(self, mock_nf_delete, mr_aai_mock): + mr_aai_mock.return_value = self.mr_aai_events + aai_handler = AAIEventHandler(self.app) + network_function = NetworkFunctionModel( + nf_name="pnf_to_be_deleted1", + ipv4_address='204.120.0.15', + ipv6_address='2001:db8:3333:4444:5555:6666:7777:8888', + model_invariant_id='123', + model_version_id='234', + model_name='pnf', + sdnc_model_name='p-node', + sdnc_model_version='v1') + db.session.add(network_function) + network_function2 = copy.deepcopy(network_function) + network_function2.nf_name = "pnf_to_be_deleted2" + db.session.add(network_function2) + db.session.commit() + aai_handler.execute() + self.assertEqual(mock_nf_delete.call_count, 2) + + @patch('mod.aai_event_handler.AAIEventHandler.apply_nfs_to_subscriptions') + @patch('mod.network_function.NetworkFunction.set_nf_model_params') + @patch('mod.pmsh_config.AppConfig.get_from_topic') + def test_process_aai_update_events(self, mr_aai_mock, mock_set_sdnc_params, mock_apply_nfs): + mock_set_sdnc_params.return_value = True + mr_aai_mock.return_value = self.mr_aai_events + aai_handler = AAIEventHandler(self.app) + aai_handler.execute() + self.assertEqual(mock_apply_nfs.call_count, 1) + + @patch('mod.pmsh_config.AppConfig.publish_to_topic', MagicMock(return_value=None)) + @patch('mod.api.services.subscription_service.apply_measurement_grp_to_nfs') + @patch('mod.network_function.NetworkFunction.set_nf_model_params') + @patch('mod.pmsh_config.AppConfig.get_from_topic') + def test_process_aai_apply_nfs_to_subscriptions(self, mr_aai_mock, mock_set_sdnc_params, + apply_nfs_to_measure_grp): mock_set_sdnc_params.return_value = True - process_aai_events(self.mock_mr_sub, self.mock_mr_pub, self.mock_app, self.app_conf) - self.assertEqual(mock_activate_sub.call_count, 2) - mock_nf_delete.assert_called_once() + mr_aai_mock.return_value = self.mr_aai_events + aai_handler = AAIEventHandler(self.app) + subscription = SubscriptionModel(subscription_name='ExtraPM-All-gNB-R2B2', + operational_policy_name='operation_policy', + control_loop_name="control-loop", + status=AdministrativeState.UNLOCKED.value) + db.session.add(subscription) + db.session.commit() + generate_nf_filter_measure_grp('ExtraPM-All-gNB-R2B', 'msr_grp_name') + generate_nf_filter_measure_grp('ExtraPM-All-gNB-R2B2', 'msr_grp_name2') + db.session.commit() + aai_handler.execute() + self.assertEqual(apply_nfs_to_measure_grp.call_count, 2) + + @patch('mod.pmsh_config.AppConfig.publish_to_topic', MagicMock(return_value=None)) + @patch('mod.api.services.subscription_service.apply_measurement_grp_to_nfs') + @patch('mod.network_function.NetworkFunction.set_nf_model_params') + @patch('mod.logger.error') + @patch('mod.pmsh_config.AppConfig.get_from_topic') + def test_process_aai_apply_msg_failure(self, mr_aai_mock, mock_logger, mock_set_sdnc_params, + apply_nfs_to_measure_grp): + mock_set_sdnc_params.return_value = True + mr_aai_mock.return_value = self.mr_aai_events + apply_nfs_to_measure_grp.side_effect = Exception("publish failed") + aai_handler = AAIEventHandler(self.app) + generate_nf_filter_measure_grp('ExtraPM-All-gNB-R2B', 'msr_grp_name3') + db.session.commit() + aai_handler.execute() + mock_logger.assert_called_with('Failed to process AAI event for ' + 'subscription: ExtraPM-All-gNB-R2B ' + 'due to: publish failed') + + @patch('mod.pmsh_config.AppConfig.publish_to_topic', MagicMock(return_value=None)) + @patch('mod.network_function.NetworkFunction.set_nf_model_params') + @patch('mod.logger.error') + @patch('mod.pmsh_config.AppConfig.get_from_topic') + def test_process_aai__failure(self, mr_aai_mock, mock_logger, mock_set_sdnc_params): + mock_set_sdnc_params.return_value = True + mr_aai_mock.side_effect = Exception("AAI failure") + aai_handler = AAIEventHandler(self.app) + generate_nf_filter_measure_grp('ExtraPM-All-gNB-R2B', 'msr_grp_name3') + db.session.commit() + aai_handler.execute() + mock_logger.assert_called_with('Failed to process AAI event due to: AAI failure') + + +def generate_nf_filter_measure_grp(sub_name, msg_name): + nf_filter = NetworkFunctionFilterModel( + subscription_name=sub_name, nf_names='{^pnf.*, ^vnf.*}', + model_invariant_ids='{}', + model_version_ids='{}', + model_names='{}') + measurement_group = MeasurementGroupModel( + subscription_name=sub_name, measurement_group_name=msg_name, + administrative_state='UNLOCKED', file_based_gp=15, file_location='pm.xml', + measurement_type=[], managed_object_dns_basic=[]) + db.session.add(nf_filter) + db.session.add(measurement_group) diff --git a/components/pm-subscription-handler/tests/test_controller.py b/components/pm-subscription-handler/tests/test_controller.py index 7bd72a29..962e8fb2 100755 --- a/components/pm-subscription-handler/tests/test_controller.py +++ b/components/pm-subscription-handler/tests/test_controller.py @@ -20,13 +20,16 @@ import os from unittest.mock import patch, MagicMock from http import HTTPStatus -from mod import aai_client -from mod.api.controller import status, post_subscription, get_subscription_by_name +from mod import aai_client, db +from mod.api.controller import status, post_subscription, get_subscription_by_name, \ + get_subscriptions, get_meas_group_with_nfs from tests.base_setup import BaseClassSetup from mod.api.db_models import SubscriptionModel, NfMeasureGroupRelationalModel from mod.subscription import SubNfState from mod.network_function import NetworkFunctionFilter -from tests.base_setup import subscription_data +from tests.base_setup import create_subscription_data, create_multiple_subscription_data, \ + create_multiple_network_function_data +from mod.api.services import measurement_group_service, nf_service class ControllerTestCase(BaseClassSetup): @@ -111,11 +114,11 @@ class ControllerTestCase(BaseClassSetup): self.assertEqual(response[1], 400) self.assertEqual(response[0], 'No value provided in subscription name') - @patch('mod.api.services.subscription_service.get_subscription_by_name', - MagicMock(return_value=subscription_data('sub_demo'))) + @patch('mod.api.services.subscription_service.query_subscription_by_name', + MagicMock(return_value=create_subscription_data('sub_demo'))) def test_get_subscription_by_name_api(self): sub, status_code = get_subscription_by_name('sub_demo') - self.assertEqual(status_code, HTTPStatus.OK) + self.assertEqual(status_code, HTTPStatus.OK.value) self.assertEqual(sub['subscription']['subscriptionName'], 'sub_demo') self.assertEqual(sub['subscription']['nfFilter']['nfNames'], ['^pnf.*', '^vnf.*']) @@ -125,16 +128,74 @@ class ControllerTestCase(BaseClassSetup): self.assertEqual(sub['subscription']['operationalPolicyName'], 'pmsh_operational_policy') - @patch('mod.api.services.subscription_service.get_subscription_by_name', + @patch('mod.api.services.subscription_service.query_subscription_by_name', MagicMock(return_value=None)) - def test_get_subscription_by_name_api_error(self): + def test_get_subscription_by_name_api_none(self): sub, status_code = get_subscription_by_name('sub_demo') - self.assertEqual(status_code, HTTPStatus.NOT_FOUND) + self.assertEqual(status_code, HTTPStatus.NOT_FOUND.value) self.assertEqual(sub['error'], 'Subscription was not defined with the name : sub_demo') - @patch('mod.api.services.subscription_service.get_subscription_by_name', + @patch('mod.api.services.subscription_service.query_subscription_by_name', MagicMock(side_effect=Exception('something failed'))) def test_get_subscription_by_name_api_exception(self): sub, status_code = get_subscription_by_name('sub_demo') - self.assertEqual(status_code, HTTPStatus.INTERNAL_SERVER_ERROR) + self.assertEqual(status_code, HTTPStatus.INTERNAL_SERVER_ERROR.value) + + @patch('mod.api.services.subscription_service.query_all_subscriptions', + MagicMock(return_value=create_multiple_subscription_data( + ['sub_demo_one', 'sub_demo_two']))) + def test_get_subscriptions_api(self): + subs, status_code = get_subscriptions() + self.assertEqual(status_code, HTTPStatus.OK.value) + self.assertEqual(subs[0]['subscription']['subscriptionName'], 'sub_demo_one') + self.assertEqual(subs[1]['subscription']['subscriptionName'], 'sub_demo_two') + self.assertEqual(subs[1]['subscription']['measurementGroups'][0]['measurementGroup'] + ['measurementGroupName'], 'MG1') + self.assertEqual(len(subs[1]['subscription']['measurementGroups']), 2) + self.assertEqual(len(subs), 2) + + @patch('mod.api.services.subscription_service.query_all_subscriptions', + MagicMock(return_value=None)) + def test_get_subscriptions_api_none(self): + subs, status_code = get_subscriptions() + self.assertEqual(status_code, HTTPStatus.OK.value) + self.assertEqual(subs, []) + + @patch('mod.api.services.subscription_service.query_all_subscriptions', + MagicMock(side_effect=Exception('something failed'))) + def test_get_subscriptions_api_exception(self): + subs, status_code = get_subscriptions() + self.assertEqual(status_code, HTTPStatus.INTERNAL_SERVER_ERROR.value) + + def test_get_meas_group_with_nfs_api(self): + sub = create_subscription_data('sub1') + nf_list = create_multiple_network_function_data(['pnf101', 'pnf102']) + measurement_group_service.save_measurement_group(sub.measurement_groups[0]. + serialize()['measurementGroup'], + sub.subscription_name) + for nf in nf_list: + nf_service.save_nf(nf) + measurement_group_service. \ + apply_nf_status_to_measurement_group(nf.nf_name, sub.measurement_groups[0]. + measurement_group_name, + SubNfState.PENDING_CREATE.value) + db.session.commit() + mg_with_nfs, status_code = get_meas_group_with_nfs('sub1', 'MG1') + self.assertEqual(status_code, HTTPStatus.OK.value) + self.assertEqual(mg_with_nfs['subscriptionName'], 'sub1') + self.assertEqual(mg_with_nfs['measurementGroupName'], 'MG1') + self.assertEqual(mg_with_nfs['administrativeState'], 'UNLOCKED') + self.assertEqual(len(mg_with_nfs['networkFunctions']), 2) + + def test_get_meas_group_with_nfs_api_none(self): + error, status_code = get_meas_group_with_nfs('sub1', 'MG1') + self.assertEqual(error['error'], 'measurement group was not defined with ' + 'the sub name: sub1 and meas group name: MG1') + self.assertEqual(status_code, HTTPStatus.NOT_FOUND.value) + + @patch('mod.api.services.measurement_group_service.query_meas_group_by_name', + MagicMock(side_effect=Exception('something failed'))) + def test_get_meas_group_with_nfs_api_exception(self): + error, status_code = get_meas_group_with_nfs('sub1', 'MG1') + self.assertEqual(status_code, HTTPStatus.INTERNAL_SERVER_ERROR.value) diff --git a/components/pm-subscription-handler/tests/test_policy_response_handler.py b/components/pm-subscription-handler/tests/test_policy_response_handler.py index 9bf15939..3e6abf94 100644 --- a/components/pm-subscription-handler/tests/test_policy_response_handler.py +++ b/components/pm-subscription-handler/tests/test_policy_response_handler.py @@ -15,30 +15,26 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== -from unittest.mock import patch +from unittest.mock import patch, MagicMock -from mod.api.db_models import SubscriptionModel +from mod import db from mod.network_function import NetworkFunction from mod.policy_response_handler import PolicyResponseHandler, policy_response_handle_functions from mod.subscription import AdministrativeState, SubNfState -from tests.base_setup import BaseClassSetup +from tests.base_setup import BaseClassSetup, create_subscription_data class PolicyResponseHandlerTest(BaseClassSetup): - @classmethod def setUpClass(cls): super().setUpClass() @patch('mod.create_app') - @patch('mod.pmsh_utils._MrSub') - def setUp(self, mock_mr_sub, mock_app): + def setUp(self, mock_app): super().setUp() - self.mock_policy_mr_sub = mock_mr_sub + super().setUpAppConf() self.nf = NetworkFunction(nf_name='nf1') - self.policy_response_handler = PolicyResponseHandler(self.mock_policy_mr_sub, - self.app_conf, - mock_app) + self.policy_response_handler = PolicyResponseHandler(mock_app) def tearDown(self): super().tearDown() @@ -52,46 +48,67 @@ class PolicyResponseHandlerTest(BaseClassSetup): with patch.dict(policy_response_handle_functions, {AdministrativeState.LOCKED.value: {'success': mock_delete}}): self.policy_response_handler._handle_response( - self.app_conf.subscription.subscriptionName, + 'msr_grp_name', AdministrativeState.LOCKED.value, self.nf.nf_name, 'success') - mock_delete.assert_called() - @patch('mod.subscription.Subscription.update_sub_nf_status') + @patch('mod.api.services.measurement_group_service.update_measurement_group_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.app_conf.subscription.subscriptionName, + 'msr_grp_name', AdministrativeState.LOCKED.value, self.nf.nf_name, 'failed') mock_update_sub_nf.assert_called_with( - subscription_name=self.app_conf.subscription.subscriptionName, + measurement_group_name='msr_grp_name', + status=SubNfState.DELETE_FAILED.value, nf_name=self.nf.nf_name) + + @patch('mod.network_function.NetworkFunction.delete') + def test_handle_response_locking_success(self, mock_delete): + with patch.dict(policy_response_handle_functions, + {AdministrativeState.LOCKING.value: {'success': mock_delete}}): + self.policy_response_handler._handle_response( + 'msr_grp_name', + AdministrativeState.LOCKING.value, + self.nf.nf_name, 'success') + mock_delete.assert_called() + + @patch('mod.api.services.measurement_group_service.update_measurement_group_nf_status') + def test_handle_response_locking_failed(self, mock_update_sub_nf): + with patch.dict(policy_response_handle_functions, + {AdministrativeState.LOCKING.value: {'failed': mock_update_sub_nf}}): + self.policy_response_handler._handle_response( + 'msr_grp_name', + AdministrativeState.LOCKING.value, + self.nf.nf_name, 'failed') + mock_update_sub_nf.assert_called_with( + measurement_group_name='msr_grp_name', status=SubNfState.DELETE_FAILED.value, nf_name=self.nf.nf_name) - @patch('mod.subscription.Subscription.update_sub_nf_status') + @patch('mod.api.services.measurement_group_service.update_measurement_group_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.app_conf.subscription.subscriptionName, + 'msr_grp_name', AdministrativeState.UNLOCKED.value, self.nf.nf_name, 'success') mock_update_sub_nf.assert_called_with( - subscription_name=self.app_conf.subscription.subscriptionName, + measurement_group_name='msr_grp_name', status=SubNfState.CREATED.value, nf_name=self.nf.nf_name) - @patch('mod.subscription.Subscription.update_sub_nf_status') + @patch('mod.api.services.measurement_group_service.update_measurement_group_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.app_conf.subscription.subscriptionName, + 'msr_grp_name', AdministrativeState.UNLOCKED.value, self.nf.nf_name, 'failed') mock_update_sub_nf.assert_called_with( - subscription_name=self.app_conf.subscription.subscriptionName, + measurement_group_name='msr_grp_name', status=SubNfState.CREATE_FAILED.value, nf_name=self.nf.nf_name) def test_handle_response_exception(self): @@ -99,45 +116,34 @@ class PolicyResponseHandlerTest(BaseClassSetup): '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): + @patch('mod.pmsh_config.AppConfig.get_from_topic') + def test_poll_policy_topic_calls_methods_correct_mg(self, mock_policy_mr_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', - operational_policy_name='policy-name', - control_loop_name='control-loop-name', - status=AdministrativeState.UNLOCKED.value) + '"ExtraPM-All-gNB-R2B2", "nfName": "pnf300", "message": "success", ' + '"measurementGroupName":"MG1"} }'] + mock_policy_mr_sub.return_value = response_data + sub_model = create_subscription_data('ExtraPM-All-gNB-R2B2') + db.session.add(sub_model) + db.session.commit() self.policy_response_handler.poll_policy_topic() - self.mock_policy_mr_sub.get_from_topic.assert_called() - mock_handle_response.assert_called_with(self.app_conf.subscription.subscriptionName, + mock_handle_response.assert_called_with("MG1", 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): + @patch('mod.pmsh_config.AppConfig.get_from_topic') + def test_poll_policy_topic_no_method_calls_unavailable_mg(self, mock_policy_mr_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', - operational_policy_name='policy-name', - control_loop_name='control-loop-name', - status=AdministrativeState.UNLOCKED.value) + '"Different_Subscription", "nfName": "pnf300", "message": "success",' + '"measurementGroupName":"msr_grp_name" } }'] + mock_policy_mr_sub.return_value = response_data 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.pmsh_config.AppConfig.get_from_topic', MagicMock(return_value='wrong_return')) @patch('mod.logger.error') - @patch('mod.subscription.Subscription.get') - def test_poll_policy_topic_exception(self, mock_get_sub, mock_logger): - self.mock_policy_mr_sub.get_from_topic.return_value = 'wrong_return' - mock_get_sub.return_value = SubscriptionModel(subscription_name='ExtraPM-All-gNB-R2B', - operational_policy_name='policy-name', - control_loop_name='control-loop-name', - status=AdministrativeState.UNLOCKED.value) + def test_poll_policy_topic_exception(self, mock_logger): self.policy_response_handler.poll_policy_topic() mock_logger.assert_called() diff --git a/components/pm-subscription-handler/tests/test_subscription_handler.py b/components/pm-subscription-handler/tests/test_subscription_handler.py index fe338327..1843eb44 100644 --- a/components/pm-subscription-handler/tests/test_subscription_handler.py +++ b/components/pm-subscription-handler/tests/test_subscription_handler.py @@ -61,8 +61,6 @@ class SubscriptionHandlerTest(BaseClassSetup): mock_logger.assert_called_with('Administrative State did not change ' 'in the app config: UNLOCKED') - @patch('mod.subscription_handler.SubscriptionHandler._start_aai_event_thread', - MagicMock()) @patch('mod.pmsh_utils.AppConfig.refresh_config', MagicMock(return_value=get_pmsh_config())) @patch('mod.subscription.Subscription.get_local_sub_admin_state') @patch('mod.subscription.Subscription.create_subscription_on_nfs') @@ -91,7 +89,6 @@ class SubscriptionHandlerTest(BaseClassSetup): sub_handler.execute() mock_deactivate_sub.assert_called_with(self.nfs, self.mock_mr_pub) - @patch('mod.subscription_handler.SubscriptionHandler._start_aai_event_thread', MagicMock()) @patch('mod.pmsh_utils.AppConfig.refresh_config', MagicMock(return_value=get_pmsh_config())) @patch('mod.subscription.Subscription.create_subscription_on_nfs') @patch('mod.logger.error') diff --git a/components/pm-subscription-handler/tox.ini b/components/pm-subscription-handler/tox.ini index 6f0cb630..38acbbb8 100644 --- a/components/pm-subscription-handler/tox.ini +++ b/components/pm-subscription-handler/tox.ini @@ -31,7 +31,7 @@ deps= setenv = PYTHONPATH={toxinidir}/pmsh_service:{toxinidir}/pmsh_service/mod:{toxinidir}/tests commands= - pytest --junitxml xunit-results.xml --cov pmsh_service --cov-report xml --cov-report term \ + pytest --junitxml xunit-results.xml --cov pmsh_service --cov-report xml --cov-report term-missing \ tests --verbose --cov-fail-under=70 [testenv:flake8] diff --git a/components/slice-analysis-ms/ChangeLog.md b/components/slice-analysis-ms/ChangeLog.md index 5d64d124..8d8a60d6 100644 --- a/components/slice-analysis-ms/ChangeLog.md +++ b/components/slice-analysis-ms/ChangeLog.md @@ -4,8 +4,14 @@ 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.6] - 2021/08/28 - - [DCAEGEN2-2885](https://jira.onap.org/browse/DCAEGEN2-2885) - DCAE SliceAnalysis MS - CPS Integration +## [1.0.7] - 2021/12/16 + - [DCAEGEN2-2963](https://jira.onap.org/browse/DCAEGEN2-2963) - Use onap/integration-java11 image + + - [DCAEGEN2-2966](https://jira.onap.org/browse/DCAEGEN2-2966) - Switch CBS client library to 1.8.7 - - [DCAEGEN2-2811](https://jira.onap.org/browse/DCAEGEN2-2811) - Remove security vulnerabilities + - [DCAEGEN2-3025](https://jira.onap.org/browse/DCAEGEN2-3025) - Fix null pointer exception while fetching slice-config + +## [1.0.6] - 2021/08/28 + - [DCAEGEN2-2885](https://jira.onap.org/browse/DCAEGEN2-2885) - DCAE SliceAnalysis MS - CPS Integration + - [DCAEGEN2-2811](https://jira.onap.org/browse/DCAEGEN2-2811) - Remove security vulnerabilities diff --git a/components/slice-analysis-ms/entry.sh b/components/slice-analysis-ms/entry.sh index 2859fe93..83225b94 100644 --- a/components/slice-analysis-ms/entry.sh +++ b/components/slice-analysis-ms/entry.sh @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh export trustpass=`cat /opt/app/sliceanalysisms/etc/cert/trust.pass` -java -Djavax.net.ssl.trustStore=/opt/app/sliceanalysisms/etc/cert/trust.jks -Djavax.net.ssl.trustStorePassword=$trustpass -jar /bin/application.jar
\ No newline at end of file +java -Djavax.net.ssl.trustStore=/opt/app/sliceanalysisms/etc/cert/trust.jks -Djavax.net.ssl.trustStorePassword="$trustpass" -jar /bin/application.jar diff --git a/components/slice-analysis-ms/pom.xml b/components/slice-analysis-ms/pom.xml index 23fab21e..faf60322 100644 --- a/components/slice-analysis-ms/pom.xml +++ b/components/slice-analysis-ms/pom.xml @@ -4,8 +4,8 @@ * ============LICENSE_START======================================================= * slice-analysis-ms * ================================================================================ - * Copyright (C) 2020-2021 Wipro Limited. - * ============================================================================== + * Copyright (C) 2020-2021 Wipro Limited. + * ================================================================================ * 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 @@ -20,39 +20,41 @@ * ============LICENSE_END========================================================= * *******************************************************************************/ - --> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - <parent> - <groupId>org.onap.oparent</groupId> - <artifactId>oparent</artifactId> - <version>2.0.0</version> - </parent> - <groupId>org.onap.dcaegen2.services.components</groupId> - <artifactId>slice-analysis-ms</artifactId> - <version>1.0.6-SNAPSHOT</version> - <name>dcaegen2-services-slice-analysis-ms</name> - <description>Network slice PM analyser</description> +--> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.onap.oparent</groupId> + <artifactId>oparent</artifactId> + <version>2.0.0</version> + </parent> + <groupId>org.onap.dcaegen2.services.components</groupId> + <artifactId>slice-analysis-ms</artifactId> + <version>1.0.7-SNAPSHOT</version> + <name>dcaegen2-services-slice-analysis-ms</name> + <description>Network slice PM analyser</description> <packaging>jar</packaging> - <properties> - <java.version>11</java.version> - <sdk.version>1.1.4</sdk.version> - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <maven.compiler.source>11</maven.compiler.source> - <maven.compiler.target>11</maven.compiler.target> - <docker.image.name>onap/${project.groupId}.${project.artifactId}</docker.image.name> - <!-- NEXUS RELATED SETTINGS --> - <nexusproxy>https://nexus.onap.org</nexusproxy> - <snapshots.path>content/repositories/snapshots/</snapshots.path> - <releases.path>content/repositories/releases/</releases.path> - <site.path>content/sites/site/org/onap/dcaegen2/services/${project.artifactId}/${project.version}</site.path> - <maven.build.timestamp.format>yyyyMMdd'T'HHmmss</maven.build.timestamp.format> - <sonar.coverage.jacoco.xmlReportPaths> - ${project.reporting.outputDirectory}/jacoco-ut/jacoco.xml - </sonar.coverage.jacoco.xmlReportPaths> - </properties> - <dependencies> + <properties> + <java.version>11</java.version> + <sdk.version>1.8.7</sdk.version> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <docker.image.name>onap/${project.groupId}.${project.artifactId}</docker.image.name> + <!-- NEXUS RELATED SETTINGS --> + <nexusproxy>https://nexus.onap.org</nexusproxy> + <snapshots.path>content/repositories/snapshots/</snapshots.path> + <releases.path>content/repositories/releases/</releases.path> + <site.path>content/sites/site/org/onap/dcaegen2/services/${project.artifactId}/${project.version}</site.path> + <maven.build.timestamp.format>yyyyMMdd'T'HHmmss</maven.build.timestamp.format> + <sonar.coverage.jacoco.xmlReportPaths> + ${project.reporting.outputDirectory}/jacoco-ut/jacoco.xml + </sonar.coverage.jacoco.xmlReportPaths> + </properties> + <dependencies> <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-util --> <dependency> <groupId>org.apache.tomcat</groupId> @@ -66,30 +68,30 @@ <version>2.3.1.RELEASE</version> </dependency> <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-webmvc</artifactId> - <version>5.3.7</version> - </dependency> - <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-core</artifactId> - <version>5.3.7</version> - </dependency> - <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-beans</artifactId> - <version>5.3.7</version> - </dependency> - <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-expression</artifactId> - <version>5.3.7</version> - </dependency> - <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-web</artifactId> - <version>5.3.7</version> - </dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-webmvc</artifactId> + <version>5.3.7</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-core</artifactId> + <version>5.3.7</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-beans</artifactId> + <version>5.3.7</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-expression</artifactId> + <version>5.3.7</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-web</artifactId> + <version>5.3.7</version> + </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> @@ -100,234 +102,227 @@ <artifactId>spring-data-commons</artifactId> <version>2.2.0.RELEASE</version> </dependency> - <!-- cbs client --> - <dependency> - <groupId>org.onap.dcaegen2.services.sdk.rest.services</groupId> - <artifactId>cbs-client</artifactId> - <version>${sdk.version}</version> - </dependency> - <dependency> - <groupId>org.onap.dcaegen2.services.sdk.security.crypt</groupId> - <artifactId>crypt-password</artifactId> - <version>${sdk.version}</version> - </dependency> - <dependency> - <!-- Import dependency management from Spring Boot --> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-dependencies</artifactId> - <version>2.1.3.RELEASE</version> - <type>pom</type> - <scope>import</scope> - </dependency> - <dependency> - <groupId>com.att.nsa</groupId> - <artifactId>cambriaClient</artifactId> - <version>0.0.1</version> - </dependency> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-core</artifactId> - <version>2.11.0</version> - </dependency> - <dependency> - <groupId>net.javacrumbs.json-unit</groupId> - <artifactId>json-unit-assertj</artifactId> - <version>2.14.0</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-databind</artifactId> - <version>2.11.0</version> - </dependency> - <!-- https://mvnrepository.com/artifact/javax.json/javax.json-api --> - <dependency> - <groupId>javax.json</groupId> - <artifactId>javax.json-api</artifactId> - <version>1.1.2</version> - </dependency> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-web</artifactId> - <version>2.1.3.RELEASE</version> - <exclusions> - <exclusion> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-tomcat</artifactId> - </exclusion> - </exclusions> - </dependency> - <dependency> - <groupId>org.postgresql</groupId> - <artifactId>postgresql</artifactId> - <version>42.2.13</version> - </dependency> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-data-jpa</artifactId> - <version>2.1.3.RELEASE</version> - </dependency> - <dependency> - <groupId>org.hibernate.javax.persistence</groupId> - <artifactId>hibernate-jpa-2.0-api</artifactId> - <version>1.0.1.Final</version> - </dependency> - <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans --> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-test</artifactId> - <version>2.1.3.RELEASE</version> - <scope>test</scope> - <!-- exclusions> <exclusion> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> - </exclusion> </exclusions --> - </dependency> - <!-- https://mvnrepository.com/artifact/org.mockito/mockito-core --> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> - <version>2.21.0</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>com.openpojo</groupId> - <artifactId>openpojo</artifactId> - <version>0.8.10</version> - </dependency> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>4.12</version> - <scope>test</scope> - </dependency> + <!-- cbs client --> + <dependency> + <groupId>org.onap.dcaegen2.services.sdk.rest.services</groupId> + <artifactId>cbs-client</artifactId> + <version>${sdk.version}</version> + </dependency> + <dependency> + <groupId>org.onap.dcaegen2.services.sdk.security.crypt</groupId> + <artifactId>crypt-password</artifactId> + <version>${sdk.version}</version> + </dependency> + <dependency> + <!-- Import dependency management from Spring Boot --> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-dependencies</artifactId> + <version>2.1.3.RELEASE</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <dependency> + <groupId>com.att.nsa</groupId> + <artifactId>cambriaClient</artifactId> + <version>0.0.1</version> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + <version>2.11.0</version> + </dependency> + <dependency> + <groupId>net.javacrumbs.json-unit</groupId> + <artifactId>json-unit-assertj</artifactId> + <version>2.14.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>2.11.0</version> + </dependency> + <!-- https://mvnrepository.com/artifact/javax.json/javax.json-api --> + <dependency> + <groupId>javax.json</groupId> + <artifactId>javax.json-api</artifactId> + <version>1.1.2</version> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + <version>2.1.3.RELEASE</version> + <exclusions> + <exclusion> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-tomcat</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.postgresql</groupId> + <artifactId>postgresql</artifactId> + <version>42.2.13</version> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + <version>2.1.3.RELEASE</version> + </dependency> + <dependency> + <groupId>org.hibernate.javax.persistence</groupId> + <artifactId>hibernate-jpa-2.0-api</artifactId> + <version>1.0.1.Final</version> + </dependency> + <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <version>2.1.3.RELEASE</version> + <scope>test</scope> + <!-- exclusions> <exclusion> <groupId>org.mockito</groupId> + <artifactId>mockito-core</artiifactId> </exclusion> </exclusions --> + </dependency> + <!-- https://mvnrepository.com/artifact/org.mockito/mockito-core --> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>2.21.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.openpojo</groupId> + <artifactId>openpojo</artifactId> + <version>0.8.10</version> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.12</version> + <scope>test</scope> + </dependency> <!-- https://mvnrepository.com/artifact/org.powermock/powermock-api-mockito2 --> - <dependency> - <groupId>org.powermock</groupId> - <artifactId>powermock-api-mockito2</artifactId> - <version>2.0.2</version> - <exclusions> - <exclusion> - <groupId>org.mockito</groupId> - <artifactId>mockito-all</artifactId> - </exclusion> - </exclusions> - </dependency> - <!-- https://mvnrepository.com/artifact/org.powermock/powermock-module-junit4 --> - <dependency> - <groupId>org.powermock</groupId> - <artifactId>powermock-module-junit4</artifactId> - <version>2.0.2</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.functionaljava</groupId> - <artifactId>functionaljava</artifactId> - <version>3.0</version> - </dependency> - <dependency> - <groupId>org.apache.httpcomponents</groupId> - <artifactId>httpclient</artifactId> - <version>4.5.13</version> - </dependency> - <!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils --> - <dependency> - <groupId>commons-beanutils</groupId> - <artifactId>commons-beanutils</artifactId> - <version>1.9.4</version> - </dependency> - <!-- mvnrepository.com/artifact/org.eclipse.jetty/jetty-server --> - <dependency> - <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-server</artifactId> - <version>9.4.41.v20210516</version> - </dependency> - <!-- https://mvnrepository.com/artifact/org.webjars/bootstrap --> - <dependency> - <groupId>org.webjars</groupId> - <artifactId>bootstrap</artifactId> - <version>4.3.1</version> - </dependency> - <!-- https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api --> - <dependency> - <groupId>javax.xml.bind</groupId> - <artifactId>jaxb-api</artifactId> - <version>2.3.0</version> - </dependency> - <!-- https://mvnrepository.com/artifact/org.javassist/javassist --> - <dependency> - <groupId>org.javassist</groupId> - <artifactId>javassist</artifactId> - <version>3.24.1-GA</version> - </dependency> - <dependency> - <groupId>org.apache.tomcat.embed</groupId> - <artifactId>tomcat-embed-core</artifactId> - <version>9.0.46</version> - </dependency> - <!-- https://mvnrepository.com/artifact/nl.jqno.equalsverifier/equalsverifier --> - <dependency> - <groupId>nl.jqno.equalsverifier</groupId> - <artifactId>equalsverifier</artifactId> - <version>3.5.5</version> - <scope>test</scope> - </dependency> - </dependencies> - <build> - <plugins> - <plugin> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-maven-plugin</artifactId> - <version>2.3.1.RELEASE</version> - <executions> - <execution> - <goals> - <goal>repackage</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <groupId>com.spotify</groupId> - <artifactId>docker-maven-plugin</artifactId> - <configuration> - <serverId>${onap.nexus.dockerregistry.daily}</serverId> - <imageName>${onap.nexus.dockerregistry.daily}/${docker.image.name}</imageName> - <imageTags> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-api-mockito2</artifactId> + <version>2.0.2</version> + <exclusions> + <exclusion> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + </exclusion> + </exclusions> + </dependency> + <!-- https://mvnrepository.com/artifact/org.powermock/powermock-module-junit4 --> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-module-junit4</artifactId> + <version>2.0.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.functionaljava</groupId> + <artifactId>functionaljava</artifactId> + <version>3.0</version> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + <version>4.5.13</version> + </dependency> + <!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils --> + <dependency> + <groupId>commons-beanutils</groupId> + <artifactId>commons-beanutils</artifactId> + <version>1.9.4</version> + </dependency> + <!-- mvnrepository.com/artifact/org.eclipse.jetty/jetty-server --> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-server</artifactId> + <version>9.4.41.v20210516</version> + </dependency> + <!-- https://mvnrepository.com/artifact/org.webjars/bootstrap --> + <dependency> + <groupId>org.webjars</groupId> + <artifactId>bootstrap</artifactId> + <version>4.3.1</version> + </dependency> + <!-- https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api --> + <dependency> + <groupId>javax.xml.bind</groupId> + <artifactId>jaxb-api</artifactId> + <version>2.3.0</version> + </dependency> + <!-- https://mvnrepository.com/artifact/org.javassist/javassist --> + <dependency> + <groupId>org.javassist</groupId> + <artifactId>javassist</artifactId> + <version>3.24.1-GA</version> + </dependency> + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-core</artifactId> + <version>9.0.46</version> + </dependency> + <!-- https://mvnrepository.com/artifact/nl.jqno.equalsverifier/equalsverifier --> + <dependency> + <groupId>nl.jqno.equalsverifier</groupId> + <artifactId>equalsverifier</artifactId> + <version>3.5.5</version> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <version>2.3.1.RELEASE</version> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>com.spotify</groupId> + <artifactId>docker-maven-plugin</artifactId> + <configuration> + <serverId>${onap.nexus.dockerregistry.daily}</serverId> + <imageName>${onap.nexus.dockerregistry.daily}/${docker.image.name}</imageName> + <imageTags> <imageTag>${project.version}-${maven.build.timestamp}Z</imageTag> <imageTag>${project.version}</imageTag> <imageTag>latest</imageTag> - </imageTags> - <baseImage>openjdk:11.0.6-jre-slim</baseImage> - <user>sliceanalysis</user> - <resources> - <resource> - <targetPath>/bin</targetPath> - <directory>${project.build.directory}</directory> - <include>${project.artifactId}-${project.version}.jar</include> - </resource> - <resource> - <targetPath>/bin</targetPath> - <directory>${project.basedir}</directory> - <include>entry.sh</include> - </resource> - </resources> - <runs> - <!-- Maven is loosing file permissions during artifacts copy --> - <run>adduser --disabled-password sliceanalysis </run> - <run>mv /bin/*.jar /bin/application.jar</run> - <run>chmod -R 777 /bin</run> - <run>chmod +x /bin/entry.sh</run> - </runs> - <exposes> - <expose>8080</expose> - </exposes> - <entryPoint>./bin/entry.sh</entryPoint> - </configuration> - </plugin> - </plugins> - </build> + </imageTags> + <baseImage>nexus3.onap.org:10001/onap/integration-java11:10.0.0</baseImage> + <user>sliceanalysis></user> + <dockerDirectory>${project.basedir}/src/main/docker/dockerfile</dockerDirectory> + <buildArgs> + <JAR_FILE>${project.artifactId}-${project.version}.jar</JAR_FILE> + </buildArgs> + <resources> + <resource> + <targetPath>/bin</targetPath> + <directory>${project.build.directory}</directory> + <include>${project.artifactId}-${project.version}.jar</include> + </resource> + <resource> + <targetPath>/bin</targetPath> + <directory>${project.basedir}</directory> + <include>entry.sh</include> + </resource> + </resources> + </configuration> + </plugin> + </plugins> + </build> </project> diff --git a/components/slice-analysis-ms/src/main/docker/dockerfile/Dockerfile b/components/slice-analysis-ms/src/main/docker/dockerfile/Dockerfile new file mode 100644 index 00000000..fa87897c --- /dev/null +++ b/components/slice-analysis-ms/src/main/docker/dockerfile/Dockerfile @@ -0,0 +1,29 @@ +# ============LICENSE_START=================================================== +# Copyright (C) 2021 Wipro Limited +# ============================================================================ +# 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===================================================== + +FROM nexus3.onap.org:10001/onap/integration-java11:10.0.0 +ADD /bin/${JAR_FILE} /bin/ +ADD /bin/entry.sh /bin/ +USER root +RUN adduser sliceanalysis --disabled-password sliceanalysis +RUN mv /bin/*.jar /bin/application.jar +RUN chmod -R 777 /bin +RUN chmod +x /bin/entry.sh +USER sliceanalysis +EXPOSE 8080 +ENTRYPOINT /bin/entry.sh diff --git a/components/slice-analysis-ms/src/main/java/org/onap/slice/analysis/ms/configdb/ConfigDbInterfaceService.java b/components/slice-analysis-ms/src/main/java/org/onap/slice/analysis/ms/configdb/ConfigDbInterfaceService.java index 5798a40f..50a60ac3 100644 --- a/components/slice-analysis-ms/src/main/java/org/onap/slice/analysis/ms/configdb/ConfigDbInterfaceService.java +++ b/components/slice-analysis-ms/src/main/java/org/onap/slice/analysis/ms/configdb/ConfigDbInterfaceService.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * slice-analysis-ms * ================================================================================ - * Copyright (C) 2020 Wipro Limited. + * Copyright (C) 2020-2021 Wipro Limited. * ============================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import org.onap.slice.analysis.ms.models.Configuration; import org.onap.slice.analysis.ms.models.configdb.CellsModel; @@ -35,82 +36,97 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; /** - * + * * Service for config db interfaces * */ @Service public class ConfigDbInterfaceService implements IConfigDbService { - @Autowired - private ConfigDbRestClient restclient; - private String configDbBaseUrl = Configuration.getInstance().getConfigDbService(); + @Autowired + private ConfigDbRestClient restclient; + private String configDbBaseUrl = Configuration.getInstance().getConfigDbService(); + + /** + * Fetches the current configuration of an S-NSSAI from config DB + */ + public Map<String, Integer> fetchCurrentConfigurationOfSlice(String snssai){ + Map<String,Integer> responseMap = null; + String reqUrl=configDbBaseUrl+"/api/sdnc-config-db/v4/profile-config/"+snssai; - /** - * Fetches the current configuration of an S-NSSAI from config DB - */ - public Map<String, Integer> fetchCurrentConfigurationOfSlice(String snssai){ - Map<String,Integer> responseMap = null; - String reqUrl=configDbBaseUrl+"/api/sdnc-config-db/v4/profile-config/"+snssai; + ResponseEntity<Map<String,Integer>> response=restclient.sendGetRequest(reqUrl,new ParameterizedTypeReference<Map<String, Integer>>() { + }); + responseMap=response.getBody(); + return responseMap; + } - ResponseEntity<Map<String,Integer>> response=restclient.sendGetRequest(reqUrl,new ParameterizedTypeReference<Map<String, Integer>>() { - }); - responseMap=response.getBody(); - return responseMap; - } + /** + * Fetches the current configuration of RIC from config DB + */ + public Map<String, Map<String, Object>> fetchCurrentConfigurationOfRIC(String snssai) { + String reqUrl = configDbBaseUrl + "/api/sdnc-config-db/v4/slice-config/" + snssai; + Map<String, Map<String, Object>> responseMap = new HashMap<String, Map<String, Object>>(); + ResponseEntity<Map<String, List<Map<String, Object>>>> response = restclient.sendGetRequest(reqUrl, + new ParameterizedTypeReference<Map<String, List<Map<String, Object>>>>() { + }); + if (Objects.nonNull(response)) { + for (Map.Entry<String, List<Map<String, Object>>> entry : response.getBody().entrySet()) { + List<Map<String, Object>> list = entry.getValue(); + if (!list.isEmpty()) { + list.forEach(l -> { + if (l.containsKey("nearRTRICId")) { + responseMap.put(String.valueOf(l.get("nearRTRICId")), l); + } + }); + } - /** - * Fetches the current configuration of RIC from config DB - */ - public Map<String,Map<String,Object>> fetchCurrentConfigurationOfRIC(String snssai){ - String reqUrl=configDbBaseUrl+"/api/sdnc-config-db/v4/slice-config/"+snssai; - ResponseEntity<Map<String,Map<String,Object>>> response=restclient.sendGetRequest(reqUrl, new ParameterizedTypeReference<Map<String,Map<String,Object>>>() { - }); - return response.getBody(); - } + } + } + return responseMap; + } - /** - * Fetches all the network functions of an S-NSSAI from config DB - */ - public List<String> fetchNetworkFunctionsOfSnssai(String snssai){ - List<String> responseList=new ArrayList<>(); - String reqUrl=configDbBaseUrl+"/api/sdnc-config-db/v4/du-list/"+snssai; - ResponseEntity<List<NetworkFunctionModel>> response=restclient.sendGetRequest(reqUrl, new ParameterizedTypeReference<List<NetworkFunctionModel>>() { - }); - for(NetworkFunctionModel networkFn:response.getBody()) { - responseList.add(networkFn.getgNBDUId()); - } - return responseList; - } + /** + * Fetches all the network functions of an S-NSSAI from config DB + */ + public List<String> fetchNetworkFunctionsOfSnssai(String snssai){ + List<String> responseList=new ArrayList<>(); + String reqUrl=configDbBaseUrl+"/api/sdnc-config-db/v4/du-list/"+snssai; + ResponseEntity<List<NetworkFunctionModel>> response=restclient.sendGetRequest(reqUrl, new ParameterizedTypeReference<List<NetworkFunctionModel>>() { + }); + for(NetworkFunctionModel networkFn:response.getBody()) { + responseList.add(networkFn.getgNBDUId()); + } + return responseList; + } - /** - * Fetches the RICS of an S-NSSAI from config DB - */ - public Map<String, List<String>> fetchRICsOfSnssai(String snssai){ - Map<String,List<String>> responseMap=new HashMap<>(); - String reqUrl=configDbBaseUrl+"/api/sdnc-config-db/v4/du-cell-list/"+snssai; - ResponseEntity<Map<String,List<CellsModel>>> response = restclient.sendGetRequest(reqUrl, new ParameterizedTypeReference<Map<String,List<CellsModel>>>() { - }); + /** + * Fetches the RICS of an S-NSSAI from config DB + */ + public Map<String, List<String>> fetchRICsOfSnssai(String snssai){ + Map<String,List<String>> responseMap=new HashMap<>(); + String reqUrl=configDbBaseUrl+"/api/sdnc-config-db/v4/du-cell-list/"+snssai; + ResponseEntity<Map<String,List<CellsModel>>> response = restclient.sendGetRequest(reqUrl, new ParameterizedTypeReference<Map<String,List<CellsModel>>>() { + }); - for (Map.Entry<String, List<CellsModel>> entry : response.getBody().entrySet()) { - List<String> cellslist=new ArrayList<>(); - for(CellsModel cellmodel:entry.getValue()) { - cellslist.add(cellmodel.getCellLocalId()); - } - responseMap.put(entry.getKey(), cellslist); - } - return responseMap; - } + for (Map.Entry<String, List<CellsModel>> entry : response.getBody().entrySet()) { + List<String> cellslist=new ArrayList<>(); + for(CellsModel cellmodel:entry.getValue()) { + cellslist.add(cellmodel.getCellLocalId()); + } + responseMap.put(entry.getKey(), cellslist); + } + return responseMap; + } - /** - * Fetches the details of a service - */ - public Map<String,String> fetchServiceDetails(String snssai){ - String reqUrl=configDbBaseUrl+"/api/sdnc-config-db/v4/subscriber-details/"+snssai; - ResponseEntity<Map<String,String>> response=restclient.sendGetRequest(reqUrl, new ParameterizedTypeReference<Map<String,String>>() { - }); - return response.getBody(); - } + /** + * Fetches the details of a service + */ + public Map<String,String> fetchServiceDetails(String snssai){ + String reqUrl=configDbBaseUrl+"/api/sdnc-config-db/v4/subscriber-details/"+snssai; + ResponseEntity<Map<String,String>> response=restclient.sendGetRequest(reqUrl, new ParameterizedTypeReference<Map<String,String>>() { + }); + return response.getBody(); + } } diff --git a/components/slice-analysis-ms/src/main/java/org/onap/slice/analysis/ms/controller/ConfigFetchFromCbs.java b/components/slice-analysis-ms/src/main/java/org/onap/slice/analysis/ms/controller/ConfigFetchFromCbs.java index 01fa91d0..fbb47e32 100644 --- a/components/slice-analysis-ms/src/main/java/org/onap/slice/analysis/ms/controller/ConfigFetchFromCbs.java +++ b/components/slice-analysis-ms/src/main/java/org/onap/slice/analysis/ms/controller/ConfigFetchFromCbs.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * slice-analysis-ms * ================================================================================ - * Copyright (C) 2020 Wipro Limited. + * Copyright (C) 2020-2021 Wipro Limited. * ============================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,8 @@ import java.util.Map; import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsClientFactory; import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsRequests; import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsRequest; -import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.EnvProperties; +import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsClientConfiguration; +import org.onap.dcaegen2.services.sdk.rest.services.model.logging.ImmutableRequestDiagnosticContext; import org.onap.dcaegen2.services.sdk.rest.services.model.logging.RequestDiagnosticContext; import org.onap.slice.analysis.ms.models.ConfigPolicy; import org.onap.slice.analysis.ms.models.Configuration; @@ -69,8 +70,9 @@ public class ConfigFetchFromCbs implements Runnable { log.info("getAppconfig start .."); RequestDiagnosticContext diagnosticContext = RequestDiagnosticContext.create(); // Read necessary properties from the environment - final EnvProperties env = EnvProperties.fromEnvironment(); - log.debug("environments {}", env); + final CbsClientConfiguration cbsClientConfiguration = CbsClientConfiguration.fromEnvironment(); + + log.debug("environments {}", cbsClientConfiguration); ConfigPolicy configPolicy = ConfigPolicy.getInstance(); // Polling properties @@ -79,7 +81,7 @@ public class ConfigFetchFromCbs implements Runnable { // Create the client and use it to get the configuration final CbsRequest request = CbsRequests.getAll(diagnosticContext); - return CbsClientFactory.createCbsClient(env) + return CbsClientFactory.createCbsClient(cbsClientConfiguration) .flatMapMany(cbsClient -> cbsClient.updates(request, initialDelay, period)).subscribe(jsonObject -> { log.info("configuration and policy from CBS {}", jsonObject); JsonObject config = jsonObject.getAsJsonObject("config"); diff --git a/components/slice-analysis-ms/src/main/java/org/onap/slice/analysis/ms/service/SnssaiSamplesProcessor.java b/components/slice-analysis-ms/src/main/java/org/onap/slice/analysis/ms/service/SnssaiSamplesProcessor.java index ccb34edb..d802f81d 100644 --- a/components/slice-analysis-ms/src/main/java/org/onap/slice/analysis/ms/service/SnssaiSamplesProcessor.java +++ b/components/slice-analysis-ms/src/main/java/org/onap/slice/analysis/ms/service/SnssaiSamplesProcessor.java @@ -48,181 +48,201 @@ import org.springframework.stereotype.Component; @Component @Scope("prototype") public class SnssaiSamplesProcessor { - private static Logger log = LoggerFactory.getLogger(SnssaiSamplesProcessor.class); - - @Autowired - private PolicyService policyService; - - @Autowired - private IConfigDbService configDbService; - - @Autowired - private PmDataQueue pmDataQueue; - - @Autowired - private AverageCalculator averageCalculator; - - @Autowired - private AaiInterface aaiInterface; - - @Autowired - private CpsInterface cpsInterface; - - private List<MeasurementObject> snssaiMeasurementList = new ArrayList<>(); - private Map<String, List<String>> ricToCellMapping = new HashMap<>(); - private Map<String, Map<String, Integer>> ricToPrbsMapping = new HashMap<>(); - private Map<String, Map<String, Integer>> ricToThroughputMapping = new HashMap<>(); - private int noOfSamples; - private List<String> pmsToCompute; - private Map<String, String> prbThroughputMapping = new HashMap<>(); - private int minPercentageChange; - - @PostConstruct - public void init() { - Configuration configuration = Configuration.getInstance(); - noOfSamples = configuration.getSamples(); - pmsToCompute = new ArrayList<>(); - pmsToCompute.add("PrbUsedDl"); - pmsToCompute.add("PrbUsedUl"); - prbThroughputMapping = new HashMap<>(); - prbThroughputMapping.put("PrbUsedDl", "dLThptPerSlice"); - prbThroughputMapping.put("PrbUsedUl", "uLThptPerSlice"); - minPercentageChange = configuration.getMinPercentageChange(); - } - - /** - * process the measurement data of an S-NSSAI - */ - public boolean processSamplesOfSnnsai(String snssai, List<String> networkFunctions) { - Boolean isConfigDbEnabled = (Objects.isNull(Configuration.getInstance().getConfigDbEnabled())) ? true - : Configuration.getInstance().getConfigDbEnabled(); - List<MeasurementObject> sample = null; - List<List<MeasurementObject>> samples = null; - Map<String, String> serviceDetails =null; - log.info("Network Functions {} of snssai {}", networkFunctions, snssai); - for (String nf : networkFunctions) { - log.debug("Average of samples for {}:", snssai); - samples = pmDataQueue.getSamplesFromQueue(new SubCounter(nf, snssai), noOfSamples); - if (samples != null) { - sample = averageCalculator.findAverageOfSamples(samples); - addToMeasurementList(sample); - } else { - log.info("Not enough samples present for nf {}", nf); - return false; - } - } - log.info("snssai measurement list {}", snssaiMeasurementList); - Map<String, Map<String, Object>> ricConfiguration; - Map<String, Integer> sliceConfiguration; - if (isConfigDbEnabled) { - ricToCellMapping = configDbService.fetchRICsOfSnssai(snssai); - ricConfiguration = configDbService.fetchCurrentConfigurationOfRIC(snssai); - sliceConfiguration = configDbService.fetchCurrentConfigurationOfSlice(snssai); - serviceDetails = configDbService.fetchServiceDetails(snssai); - } else { - ricToCellMapping = cpsInterface.fetchRICsOfSnssai(snssai); - ricConfiguration = cpsInterface.fetchCurrentConfigurationOfRIC(snssai); - sliceConfiguration = aaiInterface.fetchCurrentConfigurationOfSlice(snssai); - serviceDetails = aaiInterface.fetchServiceDetails(snssai); - } - log.info("RIC to Cell Mapping for {} S-NSSAI: {}", snssai, ricToCellMapping); - log.info("RIC Configuration {} and Slice Configuration {}", ricConfiguration, sliceConfiguration); - pmsToCompute.forEach(pm -> { - log.debug("processing for pm {}", pm); - sumOfPrbsAcrossCells(pm); - int sum = computeSum(pm); - computeThroughput(sliceConfiguration, sum, pm); - calculatePercentageChange(ricConfiguration, prbThroughputMapping.get(pm)); - }); - updateConfiguration(); - if (ricToThroughputMapping.size() > 0) { - AdditionalProperties<Map<String, Map<String, Integer>>> addProps = new AdditionalProperties<>(); - addProps.setResourceConfig(ricToThroughputMapping); - policyService.sendOnsetMessageToPolicy(snssai, addProps, serviceDetails); - } - return true; - } - - /** - * process the measurement data of an S-NSSAI - */ - protected void updateConfiguration() { - Iterator<Map.Entry<String, Map<String, Integer>>> it = ricToThroughputMapping.entrySet().iterator(); - Map.Entry<String, Map<String, Integer>> entry = null; - while (it.hasNext()) { - entry = it.next(); - if (entry.getValue().size() == 0) { - it.remove(); - } - } - } - - private void addToMeasurementList(List<MeasurementObject> sample) { - snssaiMeasurementList.addAll(sample); - } - - /** - * Calculate the change in the configuration value and keep the configuration - * only if it is greater than a specific limit - */ - protected void calculatePercentageChange(Map<String, Map<String, Object>> ricConfiguration, String pm) { - Iterator<Map.Entry<String, Map<String, Integer>>> it = ricToThroughputMapping.entrySet().iterator(); - Map.Entry<String, Map<String, Integer>> entry = null; - float existing = 0; - float change = 0; - while (it.hasNext()) { - entry = it.next(); - existing = (float) ((int) ricConfiguration.get(entry.getKey()).get(pm)); - change = ((Math.abs(entry.getValue().get(pm) - existing)) / existing) * 100; - if (change <= minPercentageChange) { - ricToThroughputMapping.get(entry.getKey()).remove(pm); - log.info("Removing pm data {} for RIC {}", pm, entry.getKey()); - } - } - } - - protected void sumOfPrbsAcrossCells(String pmName) { - ricToCellMapping.forEach((ric, cells) -> { - int sumOfPrbs = 0; - for (String cell : cells) { - int index = MeasurementObject.findIndex(cell, snssaiMeasurementList); - sumOfPrbs += snssaiMeasurementList.get(index).getPmData().get(pmName); - } - if (ricToPrbsMapping.containsKey(ric)) { - ricToPrbsMapping.get(ric).put(pmName, sumOfPrbs); - } else { - Map<String, Integer> pmToPrbMapping = new HashMap<>(); - pmToPrbMapping.put(pmName, sumOfPrbs); - ricToPrbsMapping.put(ric, pmToPrbMapping); - } - }); - log.info("PRBs sum computed for RIC {}", ricToPrbsMapping); - } - - protected Integer computeSum(String pm) { - return ricToPrbsMapping.entrySet().stream().map(x -> x.getValue().get(pm)).reduce(0, Integer::sum); - } - - protected void computeThroughput(Map<String, Integer> sliceConfiguration, int sum, String pm) { - Iterator<Map.Entry<String, Map<String, Integer>>> it = ricToPrbsMapping.entrySet().iterator(); - Map.Entry<String, Map<String, Integer>> entry = null; - Map<String, Integer> throughtputMap = null; - String ric = ""; - int value = 0; - while (it.hasNext()) { - entry = it.next(); - ric = entry.getKey(); - value = Math.round(((float) entry.getValue().get(pm) / sum) - * (float) sliceConfiguration.get(prbThroughputMapping.get(pm))); - if (ricToThroughputMapping.containsKey(ric)) { - ricToThroughputMapping.get(ric).put(prbThroughputMapping.get(pm), value); - } else { - throughtputMap = new HashMap<>(); - throughtputMap.put(prbThroughputMapping.get(pm), value); - ricToThroughputMapping.put(ric, throughtputMap); - } - } - log.info("Throughput computed for RIC {}", ricToThroughputMapping); - } + private static Logger log = LoggerFactory.getLogger(SnssaiSamplesProcessor.class); + + @Autowired + private PolicyService policyService; + + @Autowired + private IConfigDbService configDbService; + + @Autowired + private PmDataQueue pmDataQueue; + + @Autowired + private AverageCalculator averageCalculator; + + @Autowired + private AaiInterface aaiInterface; + + @Autowired + private CpsInterface cpsInterface; + + private List<MeasurementObject> snssaiMeasurementList = new ArrayList<>(); + private Map<String, List<String>> ricToCellMapping = new HashMap<>(); + private Map<String, Map<String, Integer>> ricToPrbsMapping = new HashMap<>(); + private Map<String, Map<String, Integer>> ricToThroughputMapping = new HashMap<>(); + private int noOfSamples; + private List<String> pmsToCompute; + private Map<String, String> prbThroughputMapping = new HashMap<>(); + private int minPercentageChange; + + @PostConstruct + public void init() { + Configuration configuration = Configuration.getInstance(); + noOfSamples = configuration.getSamples(); + pmsToCompute = new ArrayList<>(); + pmsToCompute.add("PrbUsedDl"); + pmsToCompute.add("PrbUsedUl"); + prbThroughputMapping = new HashMap<>(); + prbThroughputMapping.put("PrbUsedDl", "dLThptPerSlice"); + prbThroughputMapping.put("PrbUsedUl", "uLThptPerSlice"); + minPercentageChange = configuration.getMinPercentageChange(); + } + + /** + * process the measurement data of an S-NSSAI + */ + public boolean processSamplesOfSnnsai(String snssai, List<String> networkFunctions) { + Boolean isConfigDbEnabled = (Objects.isNull(Configuration.getInstance().getConfigDbEnabled())) ? true + : Configuration.getInstance().getConfigDbEnabled(); + List<MeasurementObject> sample = null; + List<List<MeasurementObject>> samples = null; + Map<String, String> serviceDetails =null; + log.info("Network Functions {} of snssai {}", networkFunctions, snssai); + for (String nf : networkFunctions) { + log.debug("Average of samples for {}:", snssai); + samples = pmDataQueue.getSamplesFromQueue(new SubCounter(nf, snssai), noOfSamples); + if (samples != null) { + sample = averageCalculator.findAverageOfSamples(samples); + addToMeasurementList(sample); + } else { + log.info("Not enough samples present for nf {}", nf); + return false; + } + } + log.info("snssai measurement list {}", snssaiMeasurementList); + Map<String, Map<String, Object>> ricConfiguration; + Map<String, Integer> sliceConfiguration; + if (isConfigDbEnabled) { + ricToCellMapping = configDbService.fetchRICsOfSnssai(snssai); + ricConfiguration = configDbService.fetchCurrentConfigurationOfRIC(snssai); + sliceConfiguration = configDbService.fetchCurrentConfigurationOfSlice(snssai); + serviceDetails = configDbService.fetchServiceDetails(snssai); + } else { + ricToCellMapping = cpsInterface.fetchRICsOfSnssai(snssai); + ricConfiguration = cpsInterface.fetchCurrentConfigurationOfRIC(snssai); + serviceDetails = aaiInterface.fetchServiceDetails(snssai); + sliceConfiguration = aaiInterface.fetchCurrentConfigurationOfSlice(snssai); + } + log.info("RIC to Cell Mapping for {} S-NSSAI: {}", snssai, ricToCellMapping); + log.info("RIC Configuration {} and Slice Configuration {}", ricConfiguration, sliceConfiguration); + pmsToCompute.forEach(pm -> { + log.debug("processing for pm {}", pm); + sumOfPrbsAcrossCells(pm); + int sum = computeSum(pm); + computeThroughput(sliceConfiguration, sum, pm); + calculatePercentageChange(ricConfiguration, prbThroughputMapping.get(pm)); + }); + updateConfiguration(); + if (ricToThroughputMapping.size() > 0) { + AdditionalProperties<Map<String, List<Map<String, Integer>>>> addProps = new AdditionalProperties<>(); + addProps.setResourceConfig(getChangedRIConfigFormat(ricToThroughputMapping)); + policyService.sendOnsetMessageToPolicy(snssai, addProps, serviceDetails); + } + return true; + } + + /** + * change the RICConfig data format to be compatible with SDN-R + */ + protected Map<String, List<Map<String, Integer>>> getChangedRIConfigFormat( + Map<String, Map<String, Integer>> ricToThroughputMapping) { + Iterator<Map.Entry<String, Map<String, Integer>>> it = ricToThroughputMapping.entrySet().iterator(); + Map.Entry<String, Map<String, Integer>> entry = null; + List<Map<String, Integer>> ricConfigList = new ArrayList<>(); + Map<String, List<Map<String, Integer>>> ricConfigData = new HashMap<>(); + while (it.hasNext()) { + Map<String, Integer> newConfigMap = new HashMap<>(); + entry = it.next(); + newConfigMap = entry.getValue(); + newConfigMap.put("nearRTRICId", Integer.parseInt(entry.getKey())); + ricConfigList.add(newConfigMap); + } + ricConfigData.put("data", ricConfigList); + return ricConfigData; + } + + /** + * process the measurement data of an S-NSSAI + */ + protected void updateConfiguration() { + Iterator<Map.Entry<String, Map<String, Integer>>> it = ricToThroughputMapping.entrySet().iterator(); + Map.Entry<String, Map<String, Integer>> entry = null; + while (it.hasNext()) { + entry = it.next(); + if (entry.getValue().size() == 0) { + it.remove(); + } + } + } + + private void addToMeasurementList(List<MeasurementObject> sample) { + snssaiMeasurementList.addAll(sample); + } + + /** + * Calculate the change in the configuration value and keep the configuration + * only if it is greater than a specific limit + */ + protected void calculatePercentageChange(Map<String, Map<String, Object>> ricConfiguration, String pm) { + Iterator<Map.Entry<String, Map<String, Integer>>> it = ricToThroughputMapping.entrySet().iterator(); + Map.Entry<String, Map<String, Integer>> entry = null; + float existing = 0; + float change = 0; + while (it.hasNext()) { + entry = it.next(); + existing = (float) ((int) ricConfiguration.get(entry.getKey()).get(pm)); + change = ((Math.abs(entry.getValue().get(pm) - existing)) / existing) * 100; + if (change <= minPercentageChange) { + ricToThroughputMapping.get(entry.getKey()).remove(pm); + log.info("Removing pm data {} for RIC {}", pm, entry.getKey()); + } + } + } + + protected void sumOfPrbsAcrossCells(String pmName) { + ricToCellMapping.forEach((ric, cells) -> { + int sumOfPrbs = 0; + for (String cell : cells) { + int index = MeasurementObject.findIndex(cell, snssaiMeasurementList); + sumOfPrbs += snssaiMeasurementList.get(index).getPmData().get(pmName); + } + if (ricToPrbsMapping.containsKey(ric)) { + ricToPrbsMapping.get(ric).put(pmName, sumOfPrbs); + } else { + Map<String, Integer> pmToPrbMapping = new HashMap<>(); + pmToPrbMapping.put(pmName, sumOfPrbs); + ricToPrbsMapping.put(ric, pmToPrbMapping); + } + }); + log.info("PRBs sum computed for RIC {}", ricToPrbsMapping); + } + + protected Integer computeSum(String pm) { + return ricToPrbsMapping.entrySet().stream().map(x -> x.getValue().get(pm)).reduce(0, Integer::sum); + } + + protected void computeThroughput(Map<String, Integer> sliceConfiguration, int sum, String pm) { + Iterator<Map.Entry<String, Map<String, Integer>>> it = ricToPrbsMapping.entrySet().iterator(); + Map.Entry<String, Map<String, Integer>> entry = null; + Map<String, Integer> throughtputMap = null; + String ric = ""; + int value = 0; + while (it.hasNext()) { + entry = it.next(); + ric = entry.getKey(); + value = Math.round(((float) entry.getValue().get(pm) / sum) + * (float) sliceConfiguration.get(prbThroughputMapping.get(pm))); + if (ricToThroughputMapping.containsKey(ric)) { + ricToThroughputMapping.get(ric).put(prbThroughputMapping.get(pm), value); + } else { + throughtputMap = new HashMap<>(); + throughtputMap.put(prbThroughputMapping.get(pm), value); + ricToThroughputMapping.put(ric, throughtputMap); + } + } + log.info("Throughput computed for RIC {}", ricToThroughputMapping); + } } diff --git a/components/slice-analysis-ms/src/test/java/org/onap/slice/analysis/ms/configdb/ConfigDbInterfaceServiceTest.java b/components/slice-analysis-ms/src/test/java/org/onap/slice/analysis/ms/configdb/ConfigDbInterfaceServiceTest.java index 3cb0a3b7..885a9171 100644 --- a/components/slice-analysis-ms/src/test/java/org/onap/slice/analysis/ms/configdb/ConfigDbInterfaceServiceTest.java +++ b/components/slice-analysis-ms/src/test/java/org/onap/slice/analysis/ms/configdb/ConfigDbInterfaceServiceTest.java @@ -41,89 +41,100 @@ import org.springframework.http.ResponseEntity; @RunWith(org.mockito.junit.MockitoJUnitRunner.class) public class ConfigDbInterfaceServiceTest { - @InjectMocks - ConfigDbInterfaceService configdbservice; - - @Mock - ConfigDbRestClient restclient; - - @Test - public void fetchCurrentConfigurationOfSlice() { - - Map<String, Integer> responsemap=new HashMap<>(); - responsemap.put("dLThptPerSlice", 1); - responsemap.put("uLThptPerSlice", 2); - Mockito.when(restclient.sendGetRequest(Mockito.anyString(), Mockito.any())).thenReturn(new ResponseEntity<Object>(responsemap, HttpStatus.OK)); - assertEquals(responsemap, configdbservice.fetchCurrentConfigurationOfSlice("snssai")); - } - - @Test - public void fetchCurrentConfigurationOfRIC() { - Map<String,Integer> map=new HashMap<>(); - Map<String, Map<String,Integer>> responsemap=new HashMap<>(); - map.put("dLThptPerSlice", 45); - map.put("uLThptPerSlice", 50); - responsemap.put("1", map); - - Mockito.when(restclient.sendGetRequest(Mockito.anyString(), Mockito.any())).thenReturn(new ResponseEntity<Object>(responsemap, HttpStatus.OK)); - assertEquals(responsemap, configdbservice.fetchCurrentConfigurationOfRIC("snssai")); - - } - @Test - public void fetchRICsOfSnssai() { - Map<String, List<CellsModel>> response=new HashMap<>(); - List<CellsModel> cellslist=new ArrayList<>(); - List<CellsModel> cellslist1=new ArrayList<>(); - CellsModel cellsmodel1=new CellsModel(); - cellsmodel1.setCellLocalId("1111"); - CellsModel cellsmodel2=new CellsModel(); - cellsmodel2.setCellLocalId("2222"); - cellslist.add(cellsmodel1); - cellslist.add(cellsmodel2); - response.put("1", cellslist); - CellsModel cellsmodel3=new CellsModel(); - cellsmodel3.setCellLocalId("3333"); - CellsModel cellsmodel4=new CellsModel(); - cellsmodel4.setCellLocalId("4444"); - cellslist1.add(cellsmodel3); - cellslist1.add(cellsmodel4); - response.put("2", cellslist1); - Mockito.when(restclient.sendGetRequest(Mockito.anyString(), Mockito.any())).thenReturn(new ResponseEntity<Object>(response, HttpStatus.OK)); - List<String> outputlist=new ArrayList<>(); - outputlist.add("1111"); - outputlist.add("2222"); - Map<String,List<String>> output= configdbservice.fetchRICsOfSnssai("snssai"); - assertEquals(outputlist, output.get("1")); - - } - - @Test - public void fetchNetworkFunctionsOfSnssai() { - - List<String> responsemap=new ArrayList<>(); - List<NetworkFunctionModel> networkfunctionslist=new ArrayList<NetworkFunctionModel>(); - NetworkFunctionModel nf1=new NetworkFunctionModel(); - nf1.setgNBDUId("1111"); - NetworkFunctionModel nf2=new NetworkFunctionModel(); - nf2.setgNBDUId("2222"); - NetworkFunctionModel nf3=new NetworkFunctionModel(); - nf3.setgNBDUId("3333"); - networkfunctionslist.add(nf1); - networkfunctionslist.add(nf2); - networkfunctionslist.add(nf3); - Mockito.when(restclient.sendGetRequest(Mockito.anyString(), Mockito.any())).thenReturn(new ResponseEntity<Object>(networkfunctionslist, HttpStatus.OK)); - responsemap=configdbservice.fetchNetworkFunctionsOfSnssai("snssai"); - assertEquals(3, responsemap.size()); - - } - public void fetchServiceProfile() { - Map<String,String> responseMap=new HashMap<String, String>(); - responseMap.put("sNSSAI", "001-010"); - responseMap.put("ranNFNSSIId","1111"); - responseMap.put("sliceProfileId","2222"); - responseMap.put("globalSubscriberId","110-345"); - Mockito.when(restclient.sendGetRequest(Mockito.anyString(), Mockito.any())).thenReturn(new ResponseEntity<Object>(responseMap, HttpStatus.OK)); - assertEquals(responseMap, configdbservice.fetchServiceDetails("snssai")); - } + @InjectMocks + ConfigDbInterfaceService configdbservice; + + @Mock + ConfigDbRestClient restclient; + + @Test + public void fetchCurrentConfigurationOfSlice() { + + Map<String, Integer> responsemap=new HashMap<>(); + responsemap.put("dLThptPerSlice", 1); + responsemap.put("uLThptPerSlice", 2); + Mockito.when(restclient.sendGetRequest(Mockito.anyString(), Mockito.any())) + .thenReturn(new ResponseEntity<Object>(responsemap, HttpStatus.OK)); + assertEquals(responsemap, configdbservice.fetchCurrentConfigurationOfSlice("snssai")); + } + + @Test + public void fetchCurrentConfigurationOfRIC() { + Map<String,Integer> map=new HashMap<>(); + Map<String, Map<String,Integer>> responsemap=new HashMap<>(); + Map<String, List<Map<String,Integer>>> result =new HashMap<String, List<Map<String,Integer>>>(); + map.put("dLThptPerSlice", 45); + map.put("uLThptPerSlice", 60); + map.put("nearRTRICId",1); + responsemap.put("1", map); + List<Map<String,Integer>> list = new ArrayList<Map<String,Integer>>(); + list.add(map); + result.put("data",list); + Mockito.when(restclient.sendGetRequest(Mockito.anyString(), Mockito.any())) + .thenReturn(new ResponseEntity<Object>(result, HttpStatus.OK)); + assertEquals(responsemap, configdbservice.fetchCurrentConfigurationOfRIC("snssai")); + + } + + @Test + public void fetchRICsOfSnssai() { + Map<String, List<CellsModel>> response=new HashMap<>(); + List<CellsModel> cellslist=new ArrayList<>(); + List<CellsModel> cellslist1=new ArrayList<>(); + CellsModel cellsmodel1=new CellsModel(); + cellsmodel1.setCellLocalId("1111"); + CellsModel cellsmodel2=new CellsModel(); + cellsmodel2.setCellLocalId("2222"); + cellslist.add(cellsmodel1); + cellslist.add(cellsmodel2); + response.put("1", cellslist); + CellsModel cellsmodel3=new CellsModel(); + cellsmodel3.setCellLocalId("3333"); + CellsModel cellsmodel4=new CellsModel(); + cellsmodel4.setCellLocalId("4444"); + cellslist1.add(cellsmodel3); + cellslist1.add(cellsmodel4); + response.put("2", cellslist1); + Mockito.when(restclient.sendGetRequest(Mockito.anyString(), Mockito.any())) + .thenReturn(new ResponseEntity<Object>(response, HttpStatus.OK)); + List<String> outputlist=new ArrayList<>(); + outputlist.add("1111"); + outputlist.add("2222"); + Map<String,List<String>> output= configdbservice.fetchRICsOfSnssai("snssai"); + assertEquals(outputlist, output.get("1")); + + } + + @Test + public void fetchNetworkFunctionsOfSnssai() { + + List<String> responsemap=new ArrayList<>(); + List<NetworkFunctionModel> networkfunctionslist=new ArrayList<NetworkFunctionModel>(); + NetworkFunctionModel nf1=new NetworkFunctionModel(); + nf1.setgNBDUId("1111"); + NetworkFunctionModel nf2=new NetworkFunctionModel(); + nf2.setgNBDUId("2222"); + NetworkFunctionModel nf3=new NetworkFunctionModel(); + nf3.setgNBDUId("3333"); + networkfunctionslist.add(nf1); + networkfunctionslist.add(nf2); + networkfunctionslist.add(nf3); + Mockito.when(restclient.sendGetRequest(Mockito.anyString(), Mockito.any())) + .thenReturn(new ResponseEntity<Object>(networkfunctionslist, HttpStatus.OK)); + responsemap=configdbservice.fetchNetworkFunctionsOfSnssai("snssai"); + assertEquals(3, responsemap.size()); + + } + + public void fetchServiceProfile() { + Map<String,String> responseMap=new HashMap<String, String>(); + responseMap.put("sNSSAI", "001-010"); + responseMap.put("ranNFNSSIId","1111"); + responseMap.put("sliceProfileId","2222"); + responseMap.put("globalSubscriberId","110-345"); + Mockito.when(restclient.sendGetRequest(Mockito.anyString(), Mockito.any())) + .thenReturn(new ResponseEntity<Object>(responseMap, HttpStatus.OK)); + assertEquals(responseMap, configdbservice.fetchServiceDetails("snssai")); + } } diff --git a/components/slice-analysis-ms/version.properties b/components/slice-analysis-ms/version.properties index 2a687c49..4434ce3b 100644 --- a/components/slice-analysis-ms/version.properties +++ b/components/slice-analysis-ms/version.properties @@ -20,7 +20,7 @@ ############################################################################### major=1 minor=0 -patch=6 +patch=7 base_version=${major}.${minor}.${patch} release_version=${base_version} snapshot_version=${base_version}-SNAPSHOT |