From 36987b3ac6eccb475e9976b95a08683a86682cdc Mon Sep 17 00:00:00 2001 From: egernug Date: Wed, 23 Feb 2022 13:38:34 +0000 Subject: [PMSH] Create Measurement Group API Creates Measurement Group for an associated Subscription POST: /subscription/{subscription_name}/measurementGroups/{measurement_group_name} Measurement Group structure: { "measurementGroup": { "measurementGroupName": "string", "fileBasedGP": 0, "fileLocation": "string", "administrativeState": "LOCKED", "measurementTypes": [ { "measurementType": "string" } ], "managedObjectDNsBasic": [ { "DN": "string" } ] } } Returns: Success: 201 Invalid Data: 400 when measurement_group_name in URI and body do not match Not Found: 404 when subscription does not exist to associate measurement group to Duplicate Data: Measurement group with that name already exists Error raised for any server failure Issue-ID: DCAEGEN2-2920 Signed-off-by: egernug Change-Id: I812c5a891e9bed5433000f5da24e2667bf9a5d65 --- components/pm-subscription-handler/Changelog.md | 2 + .../pmsh_service/mod/api/controller.py | 56 ++++++++++++++++++- .../pmsh_service/mod/api/pmsh_swagger.yml | 31 +++++++++++ .../mod/api/services/measurement_group_service.py | 63 +++++++++++++++++++--- .../mod/api/services/subscription_service.py | 14 ++--- .../services/test_measurement_group_service.py | 51 +++++++++--------- .../tests/test_controller.py | 37 ++++++++++++- 7 files changed, 213 insertions(+), 41 deletions(-) diff --git a/components/pm-subscription-handler/Changelog.md b/components/pm-subscription-handler/Changelog.md index 010fb1e5..8225ec82 100755 --- a/components/pm-subscription-handler/Changelog.md +++ b/components/pm-subscription-handler/Changelog.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed * Update Filter API (DCAEGEN2-2922) * Cleaning up old App Config, subscription handler and it's subsequent calls (DCAEGEN2-3085) +* Create Measurement Group API (DCAEGEN2-2920) + ## [2.1.1] ### 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 2e811c28..1aad0519 100755 --- a/components/pm-subscription-handler/pmsh_service/mod/api/controller.py +++ b/components/pm-subscription-handler/pmsh_service/mod/api/controller.py @@ -22,7 +22,6 @@ from mod.api.services import subscription_service, measurement_group_service from connexion import NoContent from mod.api.custom_exception import InvalidDataException, DuplicateDataException, \ DataConflictException -from mod.api.services.measurement_group_service import AdministrativeState def status(): @@ -69,6 +68,58 @@ def post_subscription(body): return response +def post_meas_group(subscription_name, measurement_group_name, body): + """ + Creates a measurement group for a subscription + + Args: + subscription_name (String): Name of the subscription. + measurement_group_name (String): Name of the measurement group + body (dict): measurement group request body to save. + + Returns: + Success : NoContent, 201 + Invalid Data: Invalid message, 400 + Not Found: Subscription no found, 404 + Duplicate Data : Duplicate field detail, 409 + + Raises: + Error: If anything fails in the server. + """ + response = NoContent, HTTPStatus.CREATED.value + try: + subscription = subscription_service.query_subscription_by_name(subscription_name) + if subscription is not None: + try: + measurement_group_service.create_measurement_group(subscription, + measurement_group_name, body) + except DuplicateDataException as e: + logger.error(f'Failed to create measurement group for ' + f'{subscription_name} due to duplicate data: {e}', + exc_info=True) + response = e.args[0], HTTPStatus.CONFLICT.value + except InvalidDataException as e: + logger.error(f'Failed to create measurement group for ' + f'{subscription_name} due to invalid data: {e}', + exc_info=True) + response = e.args[0], HTTPStatus.BAD_REQUEST.value + except Exception as e: + logger.error(f'Failed to create measurement group due to exception {e}') + response = e.args[0], HTTPStatus.INTERNAL_SERVER_ERROR.value + else: + 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.value + + except Exception as exception: + 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.value + return response + + def get_subscription_by_name(subscription_name): """ Retrieves subscription based on the name @@ -169,7 +220,8 @@ def delete_meas_group_by_name(subscription_name, measurement_group_name): measurement_group_administrative_status = \ measurement_group_service.query_get_meas_group_admin_status(subscription_name, measurement_group_name) - if measurement_group_administrative_status == AdministrativeState.LOCKED.value: + if measurement_group_administrative_status == \ + measurement_group_service.AdministrativeState.LOCKED.value: if measurement_group_service.query_to_delete_meas_group(subscription_name, measurement_group_name) == 1: return None, HTTPStatus.NO_CONTENT 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 1f24f171..258ca51e 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 @@ -160,6 +160,37 @@ paths: description: Exception occurred while querying database /subscription/{subscription_name}/measurementGroups/{measurement_group_name}: + post: + description: Create a measurement group for a given subscription + operationId: mod.api.controller.post_meas_group + 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: + $ref: "#/definitions/measurementGroup" + responses: + 201: + description: Successfully created measurement group + 404: + description: Subscription with the specified name not found + 409: + description: Duplicate data + 500: + description: Internal server error + get: description: Get the measurement group and associated network functions from PMSH by using sub name and meas group name 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 145a492c..29c4a27a 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 @@ -16,9 +16,10 @@ # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== -from mod.api.custom_exception import InvalidDataException, DataConflictException -from mod.api.db_models import MeasurementGroupModel, NfMeasureGroupRelationalModel, \ - SubscriptionModel +from mod.api.custom_exception import InvalidDataException, \ + DataConflictException, DuplicateDataException +from mod.api.db_models import MeasurementGroupModel, \ + NfMeasureGroupRelationalModel, SubscriptionModel from mod import db, logger from mod.api.services import nf_service, subscription_service from mod.network_function import NetworkFunction @@ -59,6 +60,58 @@ mg_nf_states = { } +def create_measurement_group(subscription, measurement_group_name, body): + """ + Creates a measurement group for a subscription + + Args: + subscription (SubscriptionModel): Subscription. + measurement_group_name (String): Name of MeasGroup + body (dict): measurement group request body to save. + + """ + logger.info(f'Initiating create measurement group for: {measurement_group_name}') + check_duplication(subscription.subscription_name, measurement_group_name) + check_measurement_group_names_comply(measurement_group_name, body) + new_mg = [save_measurement_group(body, subscription.subscription_name)] + if body["administrativeState"] == AdministrativeState.UNLOCKED.value: + filtered_nfs = nf_service.capture_filtered_nfs(subscription.subscription_name) + subscription_service.add_new_filtered_nfs(filtered_nfs, new_mg, subscription) + else: + logger.info(f'Measurement Group {measurement_group_name} is not in an unlocked state') + + +def check_measurement_group_names_comply(measurement_group_name, measurement_group): + """ + Check if measurement_group_name matches the name in the URI + + Args: + measurement_group_name (String): Name of the measurement group + measurement_group (dict): Measurement Group + + """ + if measurement_group_name != measurement_group["measurementGroupName"]: + logger.info(f'Changing measurement_group_name in body to {measurement_group_name}') + measurement_group["measurementGroupName"] = measurement_group_name + + +def check_duplication(subscription_name, measurement_group_name): + """ + Check if measurement group exists already + + Args: + measurement_group_name (String): Name of the measurement group + subscription_name (string) : subscription name to associate with measurement group. + + Raises: + DuplicateDataException: exception containing the detail on duplicate data field. + """ + logger.info(f"Checking that measurement group {measurement_group_name} does not exist") + if query_meas_group_by_name(subscription_name, measurement_group_name): + raise DuplicateDataException(f'Measurement Group Name: ' + f'{measurement_group_name} already exists.') + + def save_measurement_group(measurement_group, subscription_name): """ Saves the measurement_group data request @@ -165,10 +218,6 @@ def delete_nf_to_measurement_group(nf_name, measurement_group_name, status): 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}') 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 99b72dfb..6216a803 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 @@ -23,7 +23,6 @@ from mod.api.db_models import SubscriptionModel, NfSubRelationalModel, \ from mod.api.services import measurement_group_service, nf_service from mod.api.custom_exception import InvalidDataException, DuplicateDataException, \ DataConflictException -from mod.api.services.measurement_group_service import MgNfState, AdministrativeState from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import joinedload @@ -155,7 +154,7 @@ def apply_measurement_grp_to_nfs(filtered_nfs, unlocked_mgs): f'{measurement_group.measurement_group_name}') measurement_group_service.apply_nf_status_to_measurement_group( nf.nf_name, measurement_group.measurement_group_name, - MgNfState.PENDING_CREATE.value) + measurement_group_service.MgNfState.PENDING_CREATE.value) def check_missing_data(subscription): @@ -261,7 +260,7 @@ def save_subscription(subscription): SubscriptionModel(subscription_name=subscription["subscriptionName"], operational_policy_name=subscription["operationalPolicyName"], control_loop_name=control_loop_name, - status=AdministrativeState.LOCKED.value) + status=measurement_group_service.AdministrativeState.LOCKED.value) db.session.add(subscription_model) return subscription_model @@ -450,7 +449,7 @@ def get_unlocked_measurement_grps(sub_model): unlocked_mgs = [] for measurement_group in sub_model.measurement_groups: if measurement_group.administrative_state \ - == AdministrativeState.UNLOCKED.value: + == measurement_group_service.AdministrativeState.UNLOCKED.value: unlocked_mgs.append(measurement_group) else: logger.info(f'No nfs added as measure_grp_name: ' @@ -474,7 +473,8 @@ def delete_filtered_nfs(del_nfs, sub_model, unlocked_mgs): for mg in unlocked_mgs: MeasurementGroupModel.query.filter( MeasurementGroupModel.measurement_group_name == mg.measurement_group_name) \ - .update({MeasurementGroupModel.administrative_state: AdministrativeState. + .update({MeasurementGroupModel.administrative_state: + measurement_group_service.AdministrativeState. FILTERING.value}, synchronize_session='evaluate') db.session.commit() nf_meas_relations = NfMeasureGroupRelationalModel.query.filter( @@ -515,7 +515,9 @@ def validate_sub_mgs_state(sub_model): DataConflictException: contains details on conflicting status in measurement group """ mg_names_processing = [mg for mg in sub_model.measurement_groups - if mg.administrative_state in [AdministrativeState.FILTERING.value, + if mg.administrative_state in [measurement_group_service. + AdministrativeState.FILTERING.value, + measurement_group_service. AdministrativeState.LOCKING.value]] if mg_names_processing: raise DataConflictException('Cannot update filter as subscription: ' 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 25ab2581..7190069e 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 @@ -20,7 +20,8 @@ import json import os from unittest.mock import patch -from mod.api.custom_exception import InvalidDataException, DataConflictException +from mod.api.custom_exception import InvalidDataException, \ + DataConflictException, DuplicateDataException from mod.api.services.measurement_group_service import MgNfState from mod.network_function import NetworkFunction, NetworkFunctionFilter from mod.pmsh_config import AppConfig @@ -146,13 +147,9 @@ class MeasurementGroupServiceTestCase(BaseClassSetup): 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): + def test_delete_nf_to_measurement_group_failure(self, nf_delete_func): nf = NetworkFunction(nf_name='pnf_test2') nf_service.save_nf(nf) db.session.commit() @@ -165,11 +162,6 @@ class MeasurementGroupServiceTestCase(BaseClassSetup): 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') @@ -311,9 +303,6 @@ class MeasurementGroupServiceTestCase(BaseClassSetup): 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') @@ -337,13 +326,28 @@ class MeasurementGroupServiceTestCase(BaseClassSetup): 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') + + def test_check_duplication_exception(self): + sub = create_subscription_data('sub') + db.session.add(sub) + try: + measurement_group_service.check_duplication('sub', 'MG1') + except DuplicateDataException as e: + self.assertEqual(e.args[0], 'Measurement Group Name: MG1 already exists.') + + def test_check_measurement_group_names_comply(self): + mg = {'subscription_name': 'sub', + 'measurementGroupName': 'MG2', + 'administrativeState': 'UNLOCKED', + 'fileBasedGP': 15, + 'fileLocation': '/pm/pm.xml', + 'measurementTypes': '[{ "measurementType": "countera" }, ' + '{ "measurementType": "counterb" }]', + 'managedObjectDNsBasic': '[{ "DN":"dna"},{"DN":"dnb"}]'} + try: + measurement_group_service.check_measurement_group_names_comply('MG1', mg) + except InvalidDataException as e: + self.assertEqual(e.args[0], 'Measurement Group Name in body does not match with URI') def test_filter_nf_to_meas_grp_for_delete(self): sub = create_subscription_data('sub') @@ -355,13 +359,12 @@ class MeasurementGroupServiceTestCase(BaseClassSetup): db.session.commit() measurement_group_service.filter_nf_to_meas_grp( "pnf_test2", "MG2", MgNfState.DELETED.value) + measurement_group_service.filter_nf_to_meas_grp("pnf_test2", "MG2", + MgNfState.DELETED.value) measurement_grp_rel = (NfMeasureGroupRelationalModel.query.filter( NfMeasureGroupRelationalModel.measurement_grp_name == 'MG2', 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) 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') diff --git a/components/pm-subscription-handler/tests/test_controller.py b/components/pm-subscription-handler/tests/test_controller.py index 797666d9..07c17be7 100755 --- a/components/pm-subscription-handler/tests/test_controller.py +++ b/components/pm-subscription-handler/tests/test_controller.py @@ -23,11 +23,12 @@ 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, update_admin_state, \ - delete_meas_group_by_name, put_nf_filter + delete_meas_group_by_name, post_meas_group, put_nf_filter from mod.api.services.measurement_group_service import query_meas_group_by_name from tests.base_setup import BaseClassSetup from mod.api.custom_exception import InvalidDataException, DataConflictException -from mod.api.db_models import SubscriptionModel, NfMeasureGroupRelationalModel +from mod.api.db_models import SubscriptionModel, \ + NfMeasureGroupRelationalModel, MeasurementGroupModel from mod.network_function import NetworkFunctionFilter from tests.base_setup import create_subscription_data, create_multiple_subscription_data, \ create_multiple_network_function_data @@ -197,6 +198,38 @@ class ControllerTestCase(BaseClassSetup): error, status_code = get_meas_group_with_nfs('sub1', 'MG1') self.assertEqual(status_code, HTTPStatus.INTERNAL_SERVER_ERROR.value) + @patch.object(aai_client, '_get_all_aai_nf_data') + @patch.object(aai_client, 'get_aai_model_data') + def test_post_meas_group(self, 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) + subscription_data = create_subscription_data('Post_MG') + measurement_grp = {'subscription_name': 'sub', + 'measurementGroupName': 'MG2', + 'administrativeState': 'UNLOCKED', + 'fileBasedGP': 15, + 'fileLocation': '/pm/pm.xml', + 'measurementTypes': '[{ "measurementType": "countera" }, ' + '{ "measurementType": "counterb" }]', + 'managedObjectDNsBasic': '[{ "DN":"dna"},{"DN":"dnb"}]'} + db.session.add(subscription_data) + db.session.commit() + db.session.remove() + _, status_code = post_meas_group('Post_MG', 'MG3', measurement_grp) + self.assertEqual(status_code, 201) + + def test_post_meas_group_with_duplicate(self): + subscription_data = create_subscription_data('Post_MG') + measurement_grp = MeasurementGroupModel('Post_MG', 'MG1', 'UNLOCKED', 15, '/pm/pm.xml', + '[{ "measurementType": "countera" }, ' + '{ "measurementType": "counterb" }]', + '[{ "DN":"dna"},{"DN":"dnb"}]') + db.session.add(subscription_data) + db.session.commit() + db.session.remove() + _, status_code = post_meas_group('Post_MG', 'MG1', measurement_grp) + self.assertEqual(status_code, 409) + def test_delete_sub_when_state_unlocked(self): subscription_unlocked_data = create_subscription_data('MG_unlocked') subscription_unlocked_data.measurement_groups[0].measurement_group_name = 'unlock' -- cgit 1.2.3-korg