From 2a21c78886d13c4266da639252d8fb899b7d34a5 Mon Sep 17 00:00:00 2001 From: SagarS Date: Thu, 27 Jan 2022 15:05:50 +0000 Subject: [DCAEGEN2] Update Administrative status for measurement group Issue-ID: DCAEGEN2-2820 Change-Id: I290693edc5061c21bab6e0706eda02acb52e38e1 Signed-off-by: SagarS --- components/pm-subscription-handler/Changelog.md | 1 + .../pmsh_service/mod/api/controller.py | 47 +++++- .../pmsh_service/mod/api/custom_exception.py | 17 +- .../pmsh_service/mod/api/db_models.py | 4 +- .../pmsh_service/mod/api/pmsh_swagger.yml | 39 ++++- .../mod/api/services/measurement_group_service.py | 145 ++++++++++++++++- .../mod/api/services/subscription_service.py | 2 +- .../pmsh_service/mod/policy_response_handler.py | 4 +- .../services/test_measurement_group_service.py | 177 ++++++++++++++++++++- .../tests/services/test_subscription_service.py | 20 +-- .../tests/test_controller.py | 75 ++++++++- 11 files changed, 496 insertions(+), 35 deletions(-) (limited to 'components/pm-subscription-handler') diff --git a/components/pm-subscription-handler/Changelog.md b/components/pm-subscription-handler/Changelog.md index db8d5758..d2630aea 100755 --- a/components/pm-subscription-handler/Changelog.md +++ b/components/pm-subscription-handler/Changelog.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Read NFS associated with MG by using MGName and subName(DCAEGEN2-2993) * Lazy loading error for nfs in read API (DCAEGEN2-3029) * Delete subscription API by Name(DCAEGEN2-2821) +* Update Administrative status for measurement group(DCAEGEN2-2820) ## [1.3.2] ### Changed 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 d887187a..96fb1b79 100755 --- a/components/pm-subscription-handler/pmsh_service/mod/api/controller.py +++ b/components/pm-subscription-handler/pmsh_service/mod/api/controller.py @@ -20,7 +20,8 @@ from http import HTTPStatus from mod import logger from mod.api.services import subscription_service, measurement_group_service from connexion import NoContent -from mod.api.custom_exception import InvalidDataException, DuplicateDataException +from mod.api.custom_exception import InvalidDataException, DuplicateDataException, \ + DataConflictException def status(): @@ -58,12 +59,12 @@ def post_subscription(body): logger.error(f'Failed to create subscription for ' f'{body["subscription"]["subscriptionName"]} due to duplicate data: {e}', exc_info=True) - response = e.duplicate_field_info, HTTPStatus.CONFLICT.value + response = e.args[0], HTTPStatus.CONFLICT.value except InvalidDataException as e: logger.error(f'Failed to create subscription for ' f'{body["subscription"]["subscriptionName"]} due to invalid data: {e}', exc_info=True) - response = e.invalid_message, HTTPStatus.BAD_REQUEST.value + response = e.args[0], HTTPStatus.BAD_REQUEST.value return response @@ -186,3 +187,43 @@ def delete_subscription_by_name(subscription_name): return {'error': f'Try again, subscription with name {subscription_name}' f'is not deleted due to following exception: {exception}'}, \ HTTPStatus.INTERNAL_SERVER_ERROR.value + + +def update_admin_state(subscription_name, measurement_group_name, body): + """ + Performs administrative state update for the respective subscription + and measurement group name + + Args: + subscription_name (String): Name of the subscription. + measurement_group_name (String): Name of the measurement group + body (dict): Request body with admin state to update. + Returns: + string, HTTPStatus: Successfully updated admin state, 200 + string, HTTPStatus: Invalid request details, 400 + string, HTTPStatus: Cannot update as Locked request is in progress, 409 + string, HTTPStatus: Exception details of server failure, 500 + """ + logger.info('Performing administration status update for measurement group ' + f'with sub name: {subscription_name} and measurement ' + f'group name: {measurement_group_name} to {body["administrativeState"]} status') + response = 'Successfully updated admin state', HTTPStatus.OK.value + try: + meas_group = measurement_group_service.query_meas_group_by_name(subscription_name, + measurement_group_name) + measurement_group_service.update_admin_status(meas_group, body["administrativeState"]) + except InvalidDataException as exception: + logger.error(exception.args[0]) + response = exception.args[0], HTTPStatus.BAD_REQUEST.value + except DataConflictException as exception: + logger.error(exception.args[0]) + response = exception.args[0], HTTPStatus.CONFLICT.value + except Exception as exception: + logger.error('Update admin status request was not processed for sub name: ' + f'{subscription_name} and meas group name: ' + f'{measurement_group_name} due to Exception : {exception}') + response = 'Update admin status request was not processed for sub name: '\ + f'{subscription_name} and meas group name: {measurement_group_name}'\ + f' due to Exception : {exception}', HTTPStatus.INTERNAL_SERVER_ERROR + + return response diff --git a/components/pm-subscription-handler/pmsh_service/mod/api/custom_exception.py b/components/pm-subscription-handler/pmsh_service/mod/api/custom_exception.py index 606d500c..2bee3ff9 100644 --- a/components/pm-subscription-handler/pmsh_service/mod/api/custom_exception.py +++ b/components/pm-subscription-handler/pmsh_service/mod/api/custom_exception.py @@ -1,5 +1,5 @@ # ============LICENSE_START=================================================== -# Copyright (C) 2021 Nordix Foundation. +# Copyright (C) 2021-2022 Nordix Foundation. # ============================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ class InvalidDataException(Exception): """ def __init__(self, invalid_message): - self.invalid_message = invalid_message + super().__init__(invalid_message) class DuplicateDataException(Exception): @@ -35,4 +35,15 @@ class DuplicateDataException(Exception): """ def __init__(self, duplicate_field_info): - self.duplicate_field_info = duplicate_field_info + super().__init__(duplicate_field_info) + + +class DataConflictException(Exception): + """Exception raised for conflicting data state in PMSH. + + Attributes: + message -- detail on conflicting data + """ + + def __init__(self, data_conflict_message): + super().__init__(data_conflict_message) 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 548e4f28..2eccbac1 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 @@ -1,5 +1,5 @@ # ============LICENSE_START=================================================== -# Copyright (C) 2020-2021 Nordix Foundation. +# Copyright (C) 2020-2022 Nordix Foundation. # ============================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -149,7 +149,6 @@ class NfSubRelationalModel(db.Model): def serialize_nf(self): nf = NetworkFunctionModel.query.filter( NetworkFunctionModel.nf_name == self.nf_name).one_or_none() - db.session.remove() return {'nf_name': self.nf_name, 'ipv4_address': nf.ipv4_address, 'ipv6_address': nf.ipv6_address, @@ -297,7 +296,6 @@ class NfMeasureGroupRelationalModel(db.Model): """ 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, 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 3319b7ef..1c4c7927 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 @@ -112,6 +112,8 @@ paths: delete: description: Deletes the Subscription from PMSH specified by Name operationId: mod.api.controller.delete_subscription_by_name + tags: + - "Subscription" parameters: - name: subscription_name in: path @@ -136,7 +138,7 @@ paths: from PMSH by using sub name and meas group name operationId: mod.api.controller.get_meas_group_with_nfs tags: - - "measurement group" + - "Measurement Group" parameters: - name : subscription_name in: path @@ -158,6 +160,41 @@ paths: 500: description: Exception occurred while querying database + /subscription/{subscription_name}/measurementGroups/{measurement_group_name}/adminState: + put: + description: Update the admin status of the Measurement Group by using sub name and measurement group name + operationId: mod.api.controller.update_admin_state + 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 + type: string + - in: "body" + name: "body" + required: true + schema: + properties: + administrativeState: + type: string + enum: [ LOCKED, UNLOCKED ] + responses: + 200: + description: Successfully updated admin state + 409: + description: Cannot update as Locked request is in progress + 400: + description: Invalid input request details + 500: + description: Exception details of server failure + definitions: subscription: type: object 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 692efe27..a1c141f8 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 @@ -1,5 +1,5 @@ # ============LICENSE_START=================================================== -# Copyright (C) 2021 Nordix Foundation. +# Copyright (C) 2021-2022 Nordix Foundation. # ============================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,11 +16,14 @@ # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== -from mod.api.db_models import MeasurementGroupModel, NfMeasureGroupRelationalModel +from mod.api.custom_exception import InvalidDataException, DataConflictException +from mod.api.db_models import MeasurementGroupModel, NfMeasureGroupRelationalModel, \ + SubscriptionModel from mod import db, logger -from mod.api.services import nf_service +from mod.api.services import nf_service, subscription_service from mod.network_function import NetworkFunction from mod.pmsh_config import MRTopic, AppConfig +from mod.subscription import AdministrativeState, SubNfState def save_measurement_group(measurement_group, subscription_name): @@ -64,16 +67,17 @@ def apply_nf_status_to_measurement_group(nf_name, measurement_group_name, status db.session.add(new_nf_measure_grp_rel) -def publish_measurement_group(sub_model, measurement_group, nf): +def publish_measurement_group(sub_model, measurement_group, nf, change_type): """ Publishes an event for measurement group against nfs to MR Args: sub_model(SubscriptionModel): Subscription model object measurement_group (MeasurementGroupModel): Measurement group to publish - nf (NetworkFunction): Network function to publish. + nf (NetworkFunction): Network function to publish + change_type (string): defines event type like CREATE or DELETE """ - event_body = nf_service.create_nf_event_body(nf, 'CREATE', sub_model) + event_body = nf_service.create_nf_event_body(nf, change_type, sub_model) event_body['subscription'] = { "administrativeState": measurement_group.administrative_state, "subscriptionName": sub_model.subscription_name, @@ -152,3 +156,132 @@ def query_meas_group_by_name(subscription_name, measurement_group_name): MeasurementGroupModel.subscription_name == subscription_name, MeasurementGroupModel.measurement_group_name == measurement_group_name).one_or_none() return meas_group + + +def lock_nf_to_meas_grp(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: + delete_nf_to_measurement_group(nf_name, measurement_group_name, status) + nf_measurement_group_rels = NfMeasureGroupRelationalModel.query.filter( + NfMeasureGroupRelationalModel.measurement_grp_name == measurement_group_name).all() + if not nf_measurement_group_rels: + MeasurementGroupModel.query.filter( + MeasurementGroupModel.measurement_group_name == measurement_group_name). \ + update({MeasurementGroupModel.administrative_state: AdministrativeState. + LOCKED.value}, synchronize_session='evaluate') + db.session.commit() + except Exception as e: + logger.error('Failed update LOCKED status for measurement group name: ' + f'{measurement_group_name} due to: {e}') + + +def deactivate_nfs(sub_model, measurement_group, nf_meas_relations): + """ + Deactivates network functions associated with measurement group + + Args: + sub_model (SubscriptionModel): Subscription model + measurement_group (MeasurementGroupModel): Measurement group to update + nf_meas_relations (list[NfMeasureGroupRelationalModel]): nf to measurement grp relations + """ + for nf in nf_meas_relations: + logger.info(f'Saving measurement group to nf name, measure_grp_name: {nf.nf_name},' + f'{measurement_group.measurement_group_name} with DELETE request') + update_measurement_group_nf_status(measurement_group.measurement_group_name, + SubNfState.PENDING_DELETE.value, nf.nf_name) + try: + network_function = NetworkFunction(**nf.serialize_meas_group_nfs()) + logger.info(f'Publishing event for nf name, measure_grp_name: {nf.nf_name},' + f'{measurement_group.measurement_group_name} with DELETE request') + publish_measurement_group(sub_model, measurement_group, network_function, 'DELETE') + except Exception as ex: + logger.error(f'Publish event failed for nf name, measure_grp_name, sub_name: ' + f'{nf.nf_name},{measurement_group.measurement_group_name}, ' + f'{sub_model.subscription_name} with error: {ex}') + + +def activate_nfs(sub_model, measurement_group): + """ + Activates network functions associated with measurement group + + Args: + sub_model (SubscriptionModel): Subscription model + measurement_group (MeasurementGroupModel): Measurement group to update + """ + new_nfs = [] + sub_nf_names = [nf.nf_name for nf in sub_model.nfs] + filtered_nfs = nf_service.capture_filtered_nfs(sub_model.subscription_name) + for nf in filtered_nfs: + if nf.nf_name not in sub_nf_names: + new_nfs.append(nf) + if new_nfs: + logger.info(f'Adding new nfs to the subscription: ' + f'{sub_model.subscription_name}') + subscription_service.save_filtered_nfs(new_nfs) + subscription_service.apply_subscription_to_nfs(new_nfs, + sub_model.subscription_name) + 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} with CREATE request') + + apply_nf_status_to_measurement_group(nf.nf_name, + measurement_group.measurement_group_name, + SubNfState.PENDING_CREATE.value) + db.session.commit() + try: + network_function = NetworkFunction(**nf.serialize_nf()) + logger.info(f'Publishing event for nf name, measure_grp_name: {nf.nf_name},' + f'{measurement_group.measurement_group_name} with CREATE request') + publish_measurement_group(sub_model, measurement_group, network_function, 'CREATE') + except Exception as ex: + logger.error(f'Publish event failed for nf name, measure_grp_name, sub_name: ' + f'{nf.nf_name},{measurement_group.measurement_group_name}, ' + f'{sub_model.subscription_name} with error: {ex}') + + +def update_admin_status(measurement_group, status): + """ + Performs administrative status updates for the measurement group + + Args: + measurement_group (MeasurementGroupModel): Measurement group to update + status (string): Admin status to update for measurement group + + Raises: + InvalidDataException: contains details on invalid fields + DataConflictException: contains details on conflicting state of a field + Exception: contains runtime error details + """ + if measurement_group is None: + raise InvalidDataException('Requested measurement group not available ' + 'for admin status update') + elif measurement_group.administrative_state == AdministrativeState.LOCKING.value: + raise DataConflictException('Cannot update admin status as Locked request is in progress' + f' for sub name: {measurement_group.subscription_name} and ' + f'meas group name: {measurement_group.measurement_group_name}') + elif measurement_group.administrative_state == status: + raise InvalidDataException(f'Measurement group is already in {status} state ' + f'for sub name: {measurement_group.subscription_name} and ' + f'meas group name: {measurement_group.measurement_group_name}') + else: + sub_model = SubscriptionModel.query.filter( + SubscriptionModel.subscription_name == measurement_group.subscription_name) \ + .one_or_none() + nf_meas_relations = NfMeasureGroupRelationalModel.query.filter( + NfMeasureGroupRelationalModel.measurement_grp_name == measurement_group. + measurement_group_name).all() + if nf_meas_relations and status == AdministrativeState.LOCKED.value: + status = AdministrativeState.LOCKING.value + measurement_group.administrative_state = status + db.session.commit() + if status == AdministrativeState.LOCKING.value: + deactivate_nfs(sub_model, measurement_group, nf_meas_relations) + elif status == AdministrativeState.UNLOCKED.value: + activate_nfs(sub_model, measurement_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 7b31de50..338ab89e 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 @@ -89,7 +89,7 @@ def publish_measurement_grp_to_nfs(sub_model, filtered_nfs, logger.info(f'Publishing event for nf name, measure_grp_name: {nf.nf_name},' f'{measurement_group.measurement_group_name}') measurement_group_service.publish_measurement_group( - sub_model, measurement_group, nf) + sub_model, measurement_group, nf, 'CREATE') except Exception as ex: logger.error(f'Publish event failed for nf name, measure_grp_name, sub_name: ' f'{nf.nf_name},{measurement_group.measurement_group_name}, ' 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 1bc58081..a0a7bd67 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-2021 Nordix Foundation. +# Copyright (C) 2020-2022 Nordix Foundation. # ============================================================================ # 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,7 @@ policy_response_handle_functions = { 'failed': measurement_group_service.update_measurement_group_nf_status }, AdministrativeState.LOCKING.value: { - 'success': measurement_group_service.delete_nf_to_measurement_group, + 'success': measurement_group_service.lock_nf_to_meas_grp, 'failed': measurement_group_service.update_measurement_group_nf_status } } 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 97353afe..f656513e 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 @@ -1,5 +1,5 @@ # ============LICENSE_START=================================================== -# Copyright (C) 2021 Nordix Foundation. +# Copyright (C) 2021-2022 Nordix Foundation. # ============================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,10 +19,13 @@ import json import os from unittest.mock import patch -from mod.network_function import NetworkFunction + +from mod.api.custom_exception import InvalidDataException, DataConflictException +from mod.network_function import NetworkFunction, NetworkFunctionFilter from mod.pmsh_config import AppConfig -from mod import db -from tests.base_setup import BaseClassSetup +from mod import db, aai_client +from tests.base_setup import BaseClassSetup, create_subscription_data, \ + create_multiple_network_function_data from mod.api.services import measurement_group_service, nf_service from mod.api.db_models import MeasurementGroupModel, NfMeasureGroupRelationalModel, \ SubscriptionModel, NetworkFunctionModel @@ -69,7 +72,7 @@ class MeasurementGroupServiceTestCase(BaseClassSetup): sub_model = SubscriptionModel('sub_publish', 'pmsh-operational-policy', 'pmsh-control-loop', 'LOCKED') measurement_group_service.publish_measurement_group( - sub_model, measurement_grp, nf_1) + sub_model, measurement_grp, nf_1, 'CREATE') mock_mr.assert_called_once_with('policy_pm_publisher', {'nfName': 'pnf_1', 'ipAddress': '2001:db8:3333:4444:5555:6666:7777:8888', @@ -191,3 +194,167 @@ class MeasurementGroupServiceTestCase(BaseClassSetup): subscription = self.subscription_request.replace('ExtraPM-All-gNB-R2B', new_sub_name) subscription = subscription.replace('msrmt_grp_name', new_msrmt_grp_name) return subscription + + @patch.object(AppConfig, 'publish_to_topic') + def test_update_admin_status_to_locking(self, mock_mr): + super().setUpAppConf() + sub = create_subscription_data('sub') + nf_list = create_multiple_network_function_data(['pnf_101', 'pnf_102']) + db.session.add(sub) + 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.CREATED.value) + db.session.commit() + measurement_group_service.update_admin_status(sub.measurement_groups[0], 'LOCKED') + meas_grp = measurement_group_service.query_meas_group_by_name('sub', 'MG1') + self.assertEqual(meas_grp.subscription_name, 'sub') + self.assertEqual(meas_grp.measurement_group_name, 'MG1') + self.assertEqual(meas_grp.administrative_state, 'LOCKING') + meas_group_nfs = db.session.query(NfMeasureGroupRelationalModel).filter( + NfMeasureGroupRelationalModel.measurement_grp_name == meas_grp.measurement_group_name)\ + .all() + for nf in meas_group_nfs: + self.assertEqual(nf.nf_measure_grp_status, SubNfState.PENDING_DELETE.value) + + @patch.object(AppConfig, 'publish_to_topic') + def test_update_admin_status_to_locked(self, mock_mr): + super().setUpAppConf() + sub = create_subscription_data('sub') + db.session.add(sub) + measurement_group_service.update_admin_status(sub.measurement_groups[0], 'LOCKED') + meas_grp = measurement_group_service.query_meas_group_by_name('sub', 'MG1') + self.assertEqual(meas_grp.subscription_name, 'sub') + self.assertEqual(meas_grp.measurement_group_name, 'MG1') + self.assertEqual(meas_grp.administrative_state, 'LOCKED') + + @patch.object(AppConfig, 'publish_to_topic') + @patch.object(aai_client, '_get_all_aai_nf_data') + @patch.object(aai_client, 'get_aai_model_data') + @patch.object(NetworkFunctionFilter, 'get_network_function_filter') + def test_update_admin_status_to_unlocked_with_no_nfs(self, mock_filter_call, + mock_model_aai, mock_aai, mock_mr): + mock_aai.return_value = json.loads(self.aai_response_data) + mock_model_aai.return_value = json.loads(self.good_model_info) + super().setUpAppConf() + sub = create_subscription_data('sub') + sub.nfs = [] + db.session.add(sub) + db.session.commit() + mock_filter_call.return_value = NetworkFunctionFilter.get_network_function_filter('sub') + measurement_group_service.update_admin_status(sub.measurement_groups[1], 'UNLOCKED') + meas_grp = measurement_group_service.query_meas_group_by_name('sub', 'MG2') + self.assertEqual(meas_grp.subscription_name, 'sub') + self.assertEqual(meas_grp.measurement_group_name, 'MG2') + self.assertEqual(meas_grp.administrative_state, 'UNLOCKED') + meas_group_nfs = db.session.query(NfMeasureGroupRelationalModel).filter( + NfMeasureGroupRelationalModel.measurement_grp_name == meas_grp.measurement_group_name)\ + .all() + for nf in meas_group_nfs: + self.assertEqual(nf.nf_measure_grp_status, SubNfState.PENDING_CREATE.value) + + @patch.object(AppConfig, 'publish_to_topic') + @patch.object(aai_client, '_get_all_aai_nf_data') + @patch.object(aai_client, 'get_aai_model_data') + @patch.object(NetworkFunctionFilter, 'get_network_function_filter') + def test_update_admin_status_to_unlocking(self, mock_filter_call, + mock_model_aai, mock_aai, mock_mr): + mock_aai.return_value = json.loads(self.aai_response_data) + mock_model_aai.return_value = json.loads(self.good_model_info) + super().setUpAppConf() + sub = create_subscription_data('sub') + db.session.add(sub) + db.session.commit() + mock_filter_call.return_value = NetworkFunctionFilter.get_network_function_filter('sub') + measurement_group_service.update_admin_status(sub.measurement_groups[1], 'UNLOCKED') + meas_grp = measurement_group_service.query_meas_group_by_name('sub', 'MG2') + self.assertEqual(meas_grp.subscription_name, 'sub') + self.assertEqual(meas_grp.measurement_group_name, 'MG2') + self.assertEqual(meas_grp.administrative_state, 'UNLOCKED') + meas_group_nfs = db.session.query(NfMeasureGroupRelationalModel).filter( + NfMeasureGroupRelationalModel.measurement_grp_name == meas_grp.measurement_group_name)\ + .all() + for nf in meas_group_nfs: + self.assertEqual(nf.nf_measure_grp_status, SubNfState.PENDING_CREATE.value) + + def test_update_admin_status_for_missing_measurement_group(self): + try: + measurement_group_service.update_admin_status(None, 'UNLOCKED') + except InvalidDataException as e: + self.assertEqual(e.args[0], 'Requested measurement group not available ' + 'for admin status update') + + def test_update_admin_status_for_data_conflict(self): + super().setUpAppConf() + sub = create_subscription_data('sub1') + sub.measurement_groups[0].administrative_state = 'LOCKING' + try: + measurement_group_service.update_admin_status(sub.measurement_groups[0], 'LOCKED') + except DataConflictException as e: + self.assertEqual(e.args[0], 'Cannot update admin status as Locked request' + ' is in progress for sub name: sub1 and ' + 'meas group name: MG1') + + def test_update_admin_status_for_same_state(self): + super().setUpAppConf() + sub = create_subscription_data('sub1') + try: + measurement_group_service.update_admin_status(sub.measurement_groups[0], 'UNLOCKED') + except InvalidDataException as e: + self.assertEqual(e.args[0], 'Measurement group is already in UNLOCKED ' + 'state for sub name: sub1 and meas group ' + 'name: MG1') + + def test_lock_nf_to_meas_grp_for_locking_with_LOCKED_update(self): + sub = create_subscription_data('sub') + sub.measurement_groups[1].administrative_state = 'LOCKING' + nf_list = create_multiple_network_function_data(['pnf_101']) + db.session.add(sub) + 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[1]. + measurement_group_name, + SubNfState.PENDING_DELETE.value) + db.session.commit() + measurement_group_service.lock_nf_to_meas_grp( + "pnf_101", "MG2", SubNfState.DELETED.value) + measurement_grp_rel = (NfMeasureGroupRelationalModel.query.filter( + NfMeasureGroupRelationalModel.measurement_grp_name == 'MG2', + NfMeasureGroupRelationalModel.nf_name == 'pnf_101').one_or_none()) + self.assertIsNone(measurement_grp_rel) + network_function = (NetworkFunctionModel.query.filter( + NetworkFunctionModel.nf_name == 'pnf_101').one_or_none()) + self.assertIsNone(network_function) + meas_grp = measurement_group_service.query_meas_group_by_name('sub', 'MG2') + self.assertEqual(meas_grp.subscription_name, 'sub') + self.assertEqual(meas_grp.measurement_group_name, 'MG2') + self.assertEqual(meas_grp.administrative_state, 'LOCKED') + + def test_lock_nf_to_meas_grp_with_no_LOCKED_update(self): + sub = create_subscription_data('sub') + sub.measurement_groups[1].administrative_state = 'LOCKING' + nf_list = create_multiple_network_function_data(['pnf_101', 'pnf_102']) + db.session.add(sub) + 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[1]. + measurement_group_name, + SubNfState.PENDING_DELETE.value) + db.session.commit() + measurement_group_service.lock_nf_to_meas_grp( + "pnf_101", "MG2", SubNfState.DELETED.value) + measurement_grp_rel = (NfMeasureGroupRelationalModel.query.filter( + NfMeasureGroupRelationalModel.measurement_grp_name == 'MG2', + NfMeasureGroupRelationalModel.nf_name == 'pnf_101').one_or_none()) + self.assertIsNone(measurement_grp_rel) + network_function = (NetworkFunctionModel.query.filter( + NetworkFunctionModel.nf_name == 'pnf_101').one_or_none()) + self.assertIsNone(network_function) + meas_grp = measurement_group_service.query_meas_group_by_name('sub', 'MG2') + self.assertEqual(meas_grp.subscription_name, 'sub') + self.assertEqual(meas_grp.measurement_group_name, 'MG2') + self.assertEqual(meas_grp.administrative_state, 'LOCKING') 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 44fb4eff..807806f8 100644 --- a/components/pm-subscription-handler/tests/services/test_subscription_service.py +++ b/components/pm-subscription-handler/tests/services/test_subscription_service.py @@ -1,5 +1,5 @@ # ============LICENSE_START=================================================== -# Copyright (C) 2021 Nordix Foundation. +# Copyright (C) 2021-2022 Nordix Foundation. # ============================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -101,7 +101,7 @@ class SubscriptionServiceTestCase(BaseClassSetup): try: subscription_service.create_subscription(subscription) except InvalidDataException as exception: - self.assertEqual(exception.invalid_message, ["AAI call failed"]) + self.assertEqual(exception.args[0], ["AAI call failed"]) # Checking Rollback on publish failure with subscription and nfs captured existing_subscription = (SubscriptionModel.query.filter( @@ -122,7 +122,7 @@ class SubscriptionServiceTestCase(BaseClassSetup): try: subscription_service.create_subscription(subscription) except InvalidDataException as exception: - self.assertEqual(exception.invalid_message, ["AAI call failed"]) + self.assertEqual(exception.args[0], ["AAI call failed"]) # Checking Rollback on AAI failure with subscription request saved existing_subscription = (SubscriptionModel.query.filter( @@ -134,7 +134,7 @@ class SubscriptionServiceTestCase(BaseClassSetup): subscription_service.create_subscription(json.loads(self.subscription_request) ['subscription']) except DuplicateDataException as exception: - self.assertEqual(exception.duplicate_field_info, + self.assertEqual(exception.args[0], "subscription Name: ExtraPM-All-gNB-R2B already exists.") def test_missing_measurement_grp_name(self): @@ -142,7 +142,7 @@ class SubscriptionServiceTestCase(BaseClassSetup): try: subscription_service.create_subscription(json.loads(subscription)['subscription']) except InvalidDataException as exception: - self.assertEqual(exception.invalid_message, + self.assertEqual(exception.args[0], "No value provided for measurement group name") def test_missing_administrative_state(self): @@ -152,7 +152,7 @@ class SubscriptionServiceTestCase(BaseClassSetup): try: subscription_service.create_subscription(subscription['subscription']) except InvalidDataException as exception: - self.assertEqual(exception.invalid_message, + self.assertEqual(exception.args[0], "No value provided for administrative state") @patch.object(subscription_service, 'save_nf_filter') @@ -319,7 +319,7 @@ class SubscriptionServiceTestCase(BaseClassSetup): try: subscription_service.check_missing_data(subscription) except InvalidDataException as invalidEx: - self.assertEqual(invalidEx.invalid_message, "No value provided in subscription name") + self.assertEqual(invalidEx.args[0], "No value provided in subscription name") def test_check_missing_data_admin_status_missing(self): subscription = self.subscription_request.replace( @@ -328,7 +328,7 @@ class SubscriptionServiceTestCase(BaseClassSetup): try: subscription_service.check_missing_data(subscription) except InvalidDataException as invalidEx: - self.assertEqual(invalidEx.invalid_message, + self.assertEqual(invalidEx.args[0], "No value provided for administrative state") def test_check_missing_data_msr_grp_name(self): @@ -337,7 +337,7 @@ class SubscriptionServiceTestCase(BaseClassSetup): try: subscription_service.check_missing_data(subscription) except InvalidDataException as invalidEx: - self.assertEqual(invalidEx.invalid_message, + self.assertEqual(invalidEx.args[0], "No value provided for measurement group name") def test_validate_nf_filter_with_no_filter_values(self): @@ -346,7 +346,7 @@ class SubscriptionServiceTestCase(BaseClassSetup): try: subscription_service.validate_nf_filter(json.loads(nfFilter)) except InvalidDataException as invalidEx: - self.assertEqual(invalidEx.invalid_message, + self.assertEqual(invalidEx.args[0], "At least one filter within nfFilter must not be empty") def test_db_string_to_list(self): diff --git a/components/pm-subscription-handler/tests/test_controller.py b/components/pm-subscription-handler/tests/test_controller.py index 1de7c175..94bfbfd6 100755 --- a/components/pm-subscription-handler/tests/test_controller.py +++ b/components/pm-subscription-handler/tests/test_controller.py @@ -22,8 +22,9 @@ from http import HTTPStatus 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, delete_subscription_by_name + get_subscriptions, get_meas_group_with_nfs, delete_subscription_by_name, update_admin_state from tests.base_setup import BaseClassSetup +from mod.api.custom_exception import InvalidDataException, DataConflictException from mod.api.db_models import SubscriptionModel, NfMeasureGroupRelationalModel from mod.subscription import SubNfState from mod.network_function import NetworkFunctionFilter @@ -252,3 +253,75 @@ class ControllerTestCase(BaseClassSetup): def test_delete_sub_exception(self): error, status_code = delete_subscription_by_name('None') self.assertEqual(status_code, HTTPStatus.INTERNAL_SERVER_ERROR.value) + + @patch('mod.pmsh_config.AppConfig.publish_to_topic', MagicMock(return_value=None)) + def test_update_admin_state_api_for_locked_update(self): + sub = create_subscription_data('sub1') + nf_list = create_multiple_network_function_data(['pnf_101', 'pnf_102']) + db.session.add(sub) + 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.CREATED.value) + db.session.commit() + response = update_admin_state('sub1', 'MG1', {'administrativeState': 'LOCKED'}) + self.assertEqual(response[1], HTTPStatus.OK.value) + self.assertEqual(response[0], 'Successfully updated admin state') + mg_with_nfs, status_code = get_meas_group_with_nfs('sub1', 'MG1') + self.assertEqual(mg_with_nfs['subscriptionName'], 'sub1') + self.assertEqual(mg_with_nfs['measurementGroupName'], 'MG1') + self.assertEqual(mg_with_nfs['administrativeState'], 'LOCKING') + for nf in mg_with_nfs['networkFunctions']: + self.assertEqual(nf['nfMgStatus'], SubNfState.PENDING_DELETE.value) + + @patch('mod.pmsh_config.AppConfig.publish_to_topic', MagicMock(return_value=None)) + @patch.object(aai_client, '_get_all_aai_nf_data') + @patch.object(aai_client, 'get_aai_model_data') + @patch.object(NetworkFunctionFilter, 'get_network_function_filter') + def test_update_admin_state_api_for_unlocked_update(self, mock_filter_call, + mock_model_aai, mock_aai): + mock_aai.return_value = json.loads(self.aai_response_data) + mock_model_aai.return_value = json.loads(self.good_model_info) + sub = create_subscription_data('sub1') + db.session.add(sub) + nf_list = create_multiple_network_function_data(['pnf_101', 'pnf_102']) + for network_function in nf_list: + db.session.add(network_function) + db.session.commit() + mock_filter_call.return_value = NetworkFunctionFilter.get_network_function_filter('sub') + response = update_admin_state('sub1', 'MG2', {'administrativeState': 'UNLOCKED'}) + self.assertEqual(response[1], HTTPStatus.OK.value) + self.assertEqual(response[0], 'Successfully updated admin state') + mg_with_nfs, status_code = get_meas_group_with_nfs('sub1', 'MG2') + self.assertEqual(mg_with_nfs['subscriptionName'], 'sub1') + self.assertEqual(mg_with_nfs['measurementGroupName'], 'MG2') + self.assertEqual(mg_with_nfs['administrativeState'], 'UNLOCKED') + for nf in mg_with_nfs['networkFunctions']: + self.assertEqual(nf['nfMgStatus'], SubNfState.PENDING_CREATE.value) + + @patch('mod.api.services.measurement_group_service.update_admin_status', + MagicMock(side_effect=InvalidDataException('Bad request'))) + def test_update_admin_state_api_invalid_data_exception(self): + error, status_code = update_admin_state('sub4', 'MG2', + {'administrativeState': 'UNLOCKED'}) + self.assertEqual(status_code, HTTPStatus.BAD_REQUEST.value) + self.assertEqual(error, 'Bad request') + + @patch('mod.api.services.measurement_group_service.update_admin_status', + MagicMock(side_effect=DataConflictException('Data conflict'))) + def test_update_admin_state_api_data_conflict_exception(self): + error, status_code = update_admin_state('sub4', 'MG2', + {'administrativeState': 'UNLOCKED'}) + self.assertEqual(status_code, HTTPStatus.CONFLICT.value) + self.assertEqual(error, 'Data conflict') + + @patch('mod.api.services.measurement_group_service.update_admin_status', + MagicMock(side_effect=Exception('Server Error'))) + def test_update_admin_state_api_exception(self): + error, status_code = update_admin_state('sub4', 'MG2', + {'administrativeState': 'UNLOCKED'}) + self.assertEqual(status_code, HTTPStatus.INTERNAL_SERVER_ERROR.value) + self.assertEqual(error, 'Update admin status request was not processed for sub name: sub4 ' + 'and meas group name: MG2 due to Exception : Server Error') -- cgit 1.2.3-korg