[PMSH] Update Filter API 79/127279/12
authorSagarS <sagar.shetty@est.tech>
Thu, 24 Feb 2022 17:07:01 +0000 (17:07 +0000)
committerSagarS <sagar.shetty@est.tech>
Wed, 2 Mar 2022 13:47:51 +0000 (13:47 +0000)
Issue-ID: DCAEGEN2-2922
Change-Id: Ibf0ef167642027429b3ba91daea60228cf5fa4c6
Signed-off-by: SagarS <sagar.shetty@est.tech>
16 files changed:
components/pm-subscription-handler/Changelog.md
components/pm-subscription-handler/pmsh_service/mod/api/controller.py
components/pm-subscription-handler/pmsh_service/mod/api/pmsh_swagger.yml
components/pm-subscription-handler/pmsh_service/mod/api/services/measurement_group_service.py
components/pm-subscription-handler/pmsh_service/mod/api/services/nf_service.py
components/pm-subscription-handler/pmsh_service/mod/api/services/subscription_service.py
components/pm-subscription-handler/pmsh_service/mod/policy_response_handler.py
components/pm-subscription-handler/pmsh_service/mod/subscription.py
components/pm-subscription-handler/pom.xml
components/pm-subscription-handler/setup.py
components/pm-subscription-handler/tests/data/create_subscription_request.json
components/pm-subscription-handler/tests/services/test_measurement_group_service.py
components/pm-subscription-handler/tests/services/test_subscription_service.py
components/pm-subscription-handler/tests/test_controller.py
components/pm-subscription-handler/tests/test_policy_response_handler.py
components/pm-subscription-handler/version.properties

index d002f22..00e9778 100755 (executable)
@@ -5,6 +5,10 @@ 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/).
 
+## [2.2.0]
+### Changed
+* Update Filter API (DCAEGEN2-2922)
+
 ## [2.1.1]
 ### Changed
 * Fixes for Flask, MarkupSafe versions + tox (DCAEGEN2-3086)
index de3aa5f..57d3e02 100755 (executable)
@@ -267,3 +267,36 @@ def update_admin_state(subscription_name, measurement_group_name, body):
                    f' due to Exception : {exception}', HTTPStatus.INTERNAL_SERVER_ERROR
 
     return response
+
+
+def put_nf_filter(subscription_name, body):
+    """
+    Performs network function filter update for the respective subscription
+
+    Args:
+        subscription_name (String): Name of the subscription.
+        body (dict): Request body with nf filter data to update.
+    Returns:
+       string, HTTPStatus: Successfully updated network function Filter, 200
+       string, HTTPStatus: Invalid request details, 400
+       string, HTTPStatus: Cannot update as Locked/Filtering request is in progress, 409
+       string, HTTPStatus: Exception details of server failure, 500
+    """
+    logger.info('Performing network function filter update for subscription '
+                f'with sub name: {subscription_name} ')
+    response = 'Successfully updated network function Filter', HTTPStatus.OK.value
+    try:
+        subscription_service.update_filter(subscription_name, body)
+    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 nf filter request was not processed for sub name: '
+                     f'{subscription_name} due to Exception : {exception}')
+        response = 'Update nf filter request was not processed for sub name: ' \
+                   f'{subscription_name} due to Exception : {exception}', \
+                   HTTPStatus.INTERNAL_SERVER_ERROR
+    return response
index 274e0eb..1f24f17 100644 (file)
@@ -131,6 +131,33 @@ paths:
         500:
           description: Exception occurred on the server
 
+  /subscription/{subscription_name}/nfFilter:
+    put:
+      tags:
+        - "Subscription"
+      description: >-
+        Update a Subscription nf filter
+      operationId: mod.api.controller.put_nf_filter
+      parameters:
+        - name: subscription_name
+          in: path
+          required: true
+          description: The name of the subscription to update nf filters
+          type: string
+        - in: "body"
+          name: "body"
+          required: true
+          schema:
+            $ref: "#/definitions/nfFilter"
+      responses:
+        201:
+          description: Successfully updated nf filter
+        409:
+          description: Conflicting data
+        400:
+          description: Invalid input
+        500:
+          description: Exception occurred while querying database
 
   /subscription/{subscription_name}/measurementGroups/{measurement_group_name}:
     get:
@@ -162,9 +189,9 @@ paths:
 
     delete:
       description: Delete a measurement group
-      operationId: mod.api.controller.delete_meas_group
+      operationId: mod.api.controller.delete_meas_group_by_name
       tags:
-        - "measurement group"
+        - "Measurement Group"
       parameters:
         - name : subscription_name
           in: path
index b272e5b..07d1b64 100644 (file)
@@ -24,6 +24,7 @@ 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
+from sqlalchemy import or_
 
 
 def save_measurement_group(measurement_group, subscription_name):
@@ -318,3 +319,35 @@ def update_admin_status(measurement_group, status):
             deactivate_nfs(sub_model, measurement_group, nf_meas_relations)
         elif status == AdministrativeState.UNLOCKED.value:
             activate_nfs(sub_model, measurement_group)
+
+
+def filter_nf_to_meas_grp(nf_name, measurement_group_name, status):
+    """ Performs successful status update for a nf under filter update
+    request for a particular subscription and measurement group
+
+    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:
+        if status == SubNfState.DELETED.value:
+            delete_nf_to_measurement_group(nf_name, measurement_group_name,
+                                           SubNfState.DELETED.value)
+        elif status == SubNfState.CREATED.value:
+            update_measurement_group_nf_status(measurement_group_name,
+                                               SubNfState.CREATED.value, nf_name)
+        nf_measurement_group_rels = NfMeasureGroupRelationalModel.query.filter(
+            NfMeasureGroupRelationalModel.measurement_grp_name == measurement_group_name,
+            or_(NfMeasureGroupRelationalModel.nf_measure_grp_status.like('PENDING_%'),
+                NfMeasureGroupRelationalModel.nf_measure_grp_status.like('%_FAILED'))
+        ).all()
+        if not nf_measurement_group_rels:
+            MeasurementGroupModel.query.filter(
+                MeasurementGroupModel.measurement_group_name == measurement_group_name). \
+                update({MeasurementGroupModel.administrative_state: AdministrativeState.
+                       UNLOCKED.value}, synchronize_session='evaluate')
+            db.session.commit()
+    except Exception as e:
+        logger.error('Failed update filter response for measurement group name: '
+                     f'{measurement_group_name}, nf name: {nf_name} due to: {e}')
index ce463ed..a3c2a03 100644 (file)
@@ -17,7 +17,7 @@
 # ============LICENSE_END=====================================================
 
 from mod import db, aai_client, logger
-from mod.api.db_models import NetworkFunctionModel
+from mod.api.db_models import NetworkFunctionModel, NetworkFunctionFilterModel
 from mod.pmsh_config import AppConfig
 from mod.network_function import NetworkFunctionFilter
 
@@ -74,3 +74,22 @@ def save_nf(nf):
                                             sdnc_model_name=nf.sdnc_model_name,
                                             sdnc_model_version=nf.sdnc_model_version)
     db.session.add(network_function)
+
+
+def save_nf_filter_update(sub_name, nf_filter):
+    """
+    Updates the network function filter for the subscription in the db
+
+    Args:
+       sub_name (String): Name of the Subscription
+       nf_filter (dict): filter object to update in the subscription
+    """
+    NetworkFunctionFilterModel.query.filter(
+        NetworkFunctionFilterModel.subscription_name == sub_name). \
+        update({NetworkFunctionFilterModel.nf_names: nf_filter['nfNames'],
+                NetworkFunctionFilterModel.model_invariant_ids: nf_filter['modelInvariantIDs'],
+                NetworkFunctionFilterModel.model_version_ids: nf_filter['modelVersionIDs'],
+                NetworkFunctionFilterModel.model_names: nf_filter['modelNames']},
+               synchronize_session='evaluate')
+    db.session.commit()
+    logger.info(f'Successfully saved filter for subscription: {sub_name}')
index 338ab89..032fc4a 100644 (file)
 
 from mod import db, logger
 from mod.api.db_models import SubscriptionModel, NfSubRelationalModel, \
-    NetworkFunctionFilterModel, NetworkFunctionModel, MeasurementGroupModel
+    NetworkFunctionFilterModel, NetworkFunctionModel, MeasurementGroupModel, \
+    NfMeasureGroupRelationalModel
 from mod.api.services import measurement_group_service, nf_service
-from mod.api.custom_exception import InvalidDataException, DuplicateDataException
+from mod.api.custom_exception import InvalidDataException, DuplicateDataException, \
+    DataConflictException
 from mod.subscription import AdministrativeState, SubNfState
 from sqlalchemy.exc import IntegrityError
 from sqlalchemy.orm import joinedload
@@ -40,32 +42,16 @@ def create_subscription(subscription):
     logger.info(f'Initiating create subscription for: {subscription["subscriptionName"]}')
     perform_validation(subscription)
     try:
-        sub_model, measurement_groups = save_subscription_request(subscription)
+        sub_model = save_subscription_request(subscription)
         db.session.commit()
         logger.info(f'Successfully saved subscription request for: '
                     f'{subscription["subscriptionName"]}')
         filtered_nfs = nf_service.capture_filtered_nfs(sub_model.subscription_name)
-        if filtered_nfs:
-            logger.info(f'Applying the filtered nfs for subscription: '
-                        f'{sub_model.subscription_name}')
-            save_filtered_nfs(filtered_nfs)
-            apply_subscription_to_nfs(filtered_nfs, sub_model.subscription_name)
-            unlocked_msmt_groups = apply_measurement_grp_to_nfs(filtered_nfs, measurement_groups)
-            db.session.commit()
-            if unlocked_msmt_groups:
-                publish_measurement_grp_to_nfs(sub_model, filtered_nfs,
-                                               unlocked_msmt_groups)
-            else:
-                logger.error(f'All measurement groups are locked for subscription: '
-                             f'{sub_model.subscription_name}, '
-                             f'please verify/check measurement groups.')
-        else:
-            logger.error(f'No network functions found for subscription: '
-                         f'{sub_model.subscription_name}, '
-                         f'please verify/check NetworkFunctionFilter.')
+        unlocked_mgs = get_unlocked_measurement_grps(sub_model)
+        add_new_filtered_nfs(filtered_nfs, unlocked_mgs, sub_model)
     except IntegrityError as e:
         db.session.rollback()
-        raise DuplicateDataException(f'DB Integrity issue encountered: {e.orig.args[0]}')
+        raise DuplicateDataException(f'DB Integrity issue encountered: {e.orig.args[0]}') from e
     except Exception as e:
         db.session.rollback()
         raise e
@@ -73,6 +59,36 @@ def create_subscription(subscription):
         db.session.remove()
 
 
+def add_new_filtered_nfs(filtered_nfs, unlocked_mgs, sub_model):
+    """
+    Inserts the filtered nfs in measurement groups of subscription
+
+    Args:
+        filtered_nfs (List[NetworkFunction]): nfs to be inserted
+        unlocked_mgs (List[MeasurementGroupModel]): mgs to be updated with new nfs
+        sub_model (SubscriptionModel): subscription model to update
+    """
+    if filtered_nfs:
+        logger.info(f'Applying the filtered nfs for subscription: '
+                    f'{sub_model.subscription_name}')
+        save_filtered_nfs(filtered_nfs)
+        apply_subscription_to_nfs(filtered_nfs, sub_model.subscription_name)
+        db.session.commit()
+        if unlocked_mgs:
+            apply_measurement_grp_to_nfs(filtered_nfs, unlocked_mgs)
+            db.session.commit()
+            publish_measurement_grp_to_nfs(sub_model, filtered_nfs,
+                                           unlocked_mgs)
+        else:
+            logger.error(f'All measurement groups are locked for subscription: '
+                         f'{sub_model.subscription_name}, '
+                         f'please verify/check measurement groups.')
+    else:
+        logger.error(f'No network functions found for subscription: '
+                     f'{sub_model.subscription_name}, '
+                     f'please verify/check NetworkFunctionFilter.')
+
+
 def publish_measurement_grp_to_nfs(sub_model, filtered_nfs,
                                    measurement_groups):
     """
@@ -124,32 +140,22 @@ def apply_subscription_to_nfs(filtered_nfs, subscription_name):
         db.session.add(new_nf_sub_rel)
 
 
-def apply_measurement_grp_to_nfs(filtered_nfs, measurement_groups):
+def apply_measurement_grp_to_nfs(filtered_nfs, unlocked_mgs):
     """
     Saves measurement groups against nfs with status as PENDING_CREATE
 
     Args:
         filtered_nfs (list[NetworkFunction]): list of filtered network functions
-        measurement_groups (list[MeasurementGroupModel]): list of measurement group
+        unlocked_mgs (list[MeasurementGroupModel]): list of measurement group
 
-    Returns:
-        list[MeasurementGroupModel]: list of Unlocked measurement groups
     """
-    unlocked_msmt_groups = []
-    for measurement_group in measurement_groups:
-        if measurement_group.administrative_state \
-                == AdministrativeState.UNLOCKED.value:
-            unlocked_msmt_groups.append(measurement_group)
-            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_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')
-    return unlocked_msmt_groups
+    for measurement_group in unlocked_mgs:
+        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_status_to_measurement_group(
+                nf.nf_name, measurement_group.measurement_group_name,
+                SubNfState.PENDING_CREATE.value)
 
 
 def check_missing_data(subscription):
@@ -217,7 +223,7 @@ def save_subscription_request(subscription):
             measurement_group_service.save_measurement_group(
                 measurement_group['measurementGroup'],
                 subscription["subscriptionName"]))
-    return sub_model, measurement_groups
+    return sub_model
 
 
 def check_duplicate_fields(subscription_name):
@@ -380,3 +386,139 @@ def query_to_delete_subscription_by_name(subscription_name):
         .filter_by(subscription_name=subscription_name).delete()
     db.session.commit()
     return effected_rows
+
+
+def is_duplicate_filter(nf_filter, db_network_filter):
+    """
+    Checks if the network function filter is unchanged for the subscription
+
+    Args:
+        nf_filter (dict): filter object to update in the subscription
+        db_network_filter (NetworkFunctionFilterModel): nf filter object from db
+
+    Returns:
+        (boolean) : True is nf filters are same else False
+    """
+    return nf_filter == db_network_filter.serialize()
+
+
+def update_filter(sub_name, nf_filter):
+    """
+    Updates the network function filter for the subscription
+
+    Args:
+        sub_name (String): Name of the Subscription
+        nf_filter (dict): filter object to update in the subscription
+
+    Returns:
+        InvalidDataException: contains details on invalid fields
+        DataConflictException: contains details on conflicting state of a field
+        Exception: contains runtime error details
+    """
+    sub_model = query_subscription_by_name(sub_name)
+    if sub_model is None:
+        raise InvalidDataException('Requested subscription is not available '
+                                   f'with sub name: {sub_name} for nf filter update')
+    if is_duplicate_filter(nf_filter, sub_model.network_filter):
+        raise InvalidDataException('Duplicate nf filter update requested for subscription '
+                                   f'with sub name: {sub_name}')
+    validate_sub_mgs_state(sub_model)
+    nf_service.save_nf_filter_update(sub_name, nf_filter)
+    del_nfs, new_nfs = extract_del_new_nfs(sub_model)
+    NfSubRelationalModel.query.filter(
+        NfSubRelationalModel.subscription_name == sub_name,
+        NfSubRelationalModel.nf_name.in_(del_nfs)).delete()
+    db.session.commit()
+    unlocked_mgs = get_unlocked_measurement_grps(sub_model)
+    if unlocked_mgs:
+        add_new_filtered_nfs(new_nfs, unlocked_mgs, sub_model)
+        delete_filtered_nfs(del_nfs, sub_model, unlocked_mgs)
+    db.session.remove()
+
+
+def get_unlocked_measurement_grps(sub_model):
+    """
+    Gets unlocked measurement groups and logs locked measurement groups
+
+    Args:
+        sub_model (SubscriptionModel): Subscription model to perform nfs delete
+
+    Returns:
+        unlocked_mgs (List[MeasurementGroupModel]): unlocked msgs in a subscription
+
+    """
+    unlocked_mgs = []
+    for measurement_group in sub_model.measurement_groups:
+        if measurement_group.administrative_state \
+                == AdministrativeState.UNLOCKED.value:
+            unlocked_mgs.append(measurement_group)
+        else:
+            logger.info(f'No nfs added as measure_grp_name: '
+                        f'{measurement_group.measurement_group_name} is LOCKED')
+    return unlocked_mgs
+
+
+def delete_filtered_nfs(del_nfs, sub_model, unlocked_mgs):
+    """
+    Removes unfiltered nfs
+
+    Args:
+        del_nfs (List[String]): Names of nfs to be deleted
+        sub_model (SubscriptionModel): Subscription model to perform nfs delete
+        unlocked_mgs (List[MeasurementGroupModel]): unlocked msgs to perform nfs delete
+
+    """
+    if del_nfs:
+        logger.info(f'Removing nfs from subscription: '
+                    f'{sub_model.subscription_name}')
+        for mg in unlocked_mgs:
+            MeasurementGroupModel.query.filter(
+                MeasurementGroupModel.measurement_group_name == mg.measurement_group_name) \
+                .update({MeasurementGroupModel.administrative_state: AdministrativeState.
+                        FILTERING.value}, synchronize_session='evaluate')
+            db.session.commit()
+            nf_meas_relations = NfMeasureGroupRelationalModel.query.filter(
+                NfMeasureGroupRelationalModel.measurement_grp_name == mg.
+                measurement_group_name, NfMeasureGroupRelationalModel.
+                nf_name.in_(del_nfs)).all()
+            measurement_group_service.deactivate_nfs(sub_model, mg, nf_meas_relations)
+
+
+def extract_del_new_nfs(sub_model):
+    """
+    Captures nfs to be deleted and created for the subscription
+
+    Args:
+        sub_model (SubscriptionModel): Subscription model to perform nfs delete
+
+    Returns:
+        del_nfs (List[String]): Names of nfs to be deleted
+        new_nfs (List[NetworkFunction]): nfs to be inserted
+    """
+    filtered_nfs = nf_service.capture_filtered_nfs(sub_model.subscription_name)
+    filtered_nf_names = [nf.nf_name for nf in filtered_nfs]
+    existing_nf_names = [nf.nf_name for nf in sub_model.nfs]
+    new_nfs = list(filter(lambda x: x.nf_name not in existing_nf_names, filtered_nfs))
+    del_nfs = [nf.nf_name for nf in sub_model.nfs if nf.nf_name not in filtered_nf_names]
+    return del_nfs, new_nfs
+
+
+def validate_sub_mgs_state(sub_model):
+    """
+    Validates if any measurement group in subscription has
+    status Locking or Filtering
+
+    Args:
+        sub_model (SubscriptionModel): Subscription model to perform validation before nf filter
+
+    Returns:
+        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,
+                                                          AdministrativeState.LOCKING.value]]
+    if mg_names_processing:
+        raise DataConflictException('Cannot update filter as subscription: '
+                                    f'{sub_model.subscription_name} is under '
+                                    'transitioning state for the following measurement '
+                                    f'groups: {mg_names_processing}')
index a0a7bd6..5065ce8 100644 (file)
@@ -18,8 +18,8 @@
 import json
 from mod.pmsh_config import MRTopic, AppConfig
 from mod import logger
-from mod.subscription import AdministrativeState, subscription_nf_states
-from mod.api.db_models import MeasurementGroupModel
+from mod.subscription import AdministrativeState, subscription_nf_states, SubNfState
+from mod.api.db_models import MeasurementGroupModel, NfMeasureGroupRelationalModel
 from mod.api.services import measurement_group_service
 
 policy_response_handle_functions = {
@@ -34,6 +34,10 @@ policy_response_handle_functions = {
     AdministrativeState.LOCKING.value: {
         'success': measurement_group_service.lock_nf_to_meas_grp,
         'failed': measurement_group_service.update_measurement_group_nf_status
+    },
+    AdministrativeState.FILTERING.value: {
+        'success': measurement_group_service.filter_nf_to_meas_grp,
+        'failed': measurement_group_service.update_measurement_group_nf_status
     }
 }
 
@@ -86,8 +90,19 @@ class PolicyResponseHandler:
         logger.info(f'Response from MR: measurement group name: {measurement_group_name} for '
                     f'NF: {nf_name} received, updating the DB')
         try:
-            nf_measure_grp_status = subscription_nf_states[administrative_state][response_message]\
-                .value
+
+            if administrative_state == AdministrativeState.FILTERING.value:
+                nf_msg_rel = NfMeasureGroupRelationalModel.query.filter(
+                    NfMeasureGroupRelationalModel.measurement_grp_name == measurement_group_name,
+                    NfMeasureGroupRelationalModel.nf_name == nf_name
+                ).one_or_none()
+                if nf_msg_rel.nf_measure_grp_status == SubNfState.PENDING_DELETE.value:
+                    administrative_state = AdministrativeState.LOCKING.value
+                elif nf_msg_rel.nf_measure_grp_status == SubNfState.PENDING_CREATE.value:
+                    administrative_state = AdministrativeState.UNLOCKED.value
+
+            nf_measure_grp_status = (subscription_nf_states[administrative_state]
+                                     [response_message]).value
             policy_response_handle_functions[administrative_state][response_message](
                 measurement_group_name=measurement_group_name, status=nf_measure_grp_status,
                 nf_name=nf_name)
index 603343f..ddb6e1f 100755 (executable)
@@ -1,5 +1,5 @@
 # ============LICENSE_START===================================================
-#  Copyright (C) 2019-2021 Nordix Foundation.
+#  Copyright (C) 2019-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.
@@ -34,6 +34,7 @@ class AdministrativeState(Enum):
     UNLOCKED = 'UNLOCKED'
     LOCKING = 'LOCKING'
     LOCKED = 'LOCKED'
+    FILTERING = 'FILTERING'
 
 
 subscription_nf_states = {
index 815b2f1..88ae50d 100644 (file)
@@ -32,7 +32,7 @@
   <groupId>org.onap.dcaegen2.services</groupId>
   <artifactId>pmsh</artifactId>
   <name>dcaegen2-services-pm-subscription-handler</name>
-  <version>2.1.1-SNAPSHOT</version>
+  <version>2.2.0-SNAPSHOT</version>
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <sonar.sources>.</sonar.sources>
index ba1b74b..a4a80ef 100644 (file)
@@ -22,7 +22,7 @@ from setuptools import setup, find_packages
 
 setup(
     name="pm_subscription_handler",
-    version="2.1.1",
+    version="2.2.0",
     packages=find_packages(),
     author="lego@est.tech",
     author_email="lego@est.tech",
@@ -43,5 +43,6 @@ setup(
         "onap_dcae_cbs_docker_client==2.2.1",
         "onappylog==1.0.9",
         "ruamel.yaml==0.16.10",
-        "jsonschema==3.2.0"]
+        "jsonschema==3.2.0",
+        "pyyaml==5.4.1"]
 )
index bc089a9..74c0937 100644 (file)
@@ -10,7 +10,8 @@
       ],
       "modelInvariantIDs": [
         "8lk4578-d396-4efb-af02-6b83499b12f8",
-        "687kj45-d396-4efb-af02-6b83499b12f8"
+        "687kj45-d396-4efb-af02-6b83499b12f8",
+        "597b524-d396-4efb-af02-6b83499b12f8"
 
       ],
       "modelVersionIDs": [
index f656513..1dbe84a 100644 (file)
@@ -358,3 +358,48 @@ class MeasurementGroupServiceTestCase(BaseClassSetup):
         self.assertEqual(meas_grp.subscription_name, 'sub')
         self.assertEqual(meas_grp.measurement_group_name, 'MG2')
         self.assertEqual(meas_grp.administrative_state, 'LOCKING')
+
+    def test_filter_nf_to_meas_grp_for_delete(self):
+        sub = create_subscription_data('sub')
+        db.session.add(sub)
+        nf = NetworkFunction(nf_name='pnf_test2')
+        nf_service.save_nf(nf)
+        measurement_group_service.apply_nf_status_to_measurement_group(
+            "pnf_test2", "MG2", SubNfState.PENDING_DELETE.value)
+        db.session.commit()
+        measurement_group_service.filter_nf_to_meas_grp(
+            "pnf_test2", "MG2", SubNfState.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')
+        self.assertEqual(meas_grp.administrative_state, 'UNLOCKED')
+
+    def test_filter_nf_to_meas_grp_for_create(self):
+        sub = create_subscription_data('sub')
+        db.session.add(sub)
+        nf = NetworkFunction(nf_name='pnf_test2')
+        nf_service.save_nf(nf)
+        measurement_group_service.apply_nf_status_to_measurement_group(
+            "pnf_test2", "MG2", SubNfState.PENDING_CREATE.value)
+        db.session.commit()
+        measurement_group_service.filter_nf_to_meas_grp(
+            "pnf_test2", "MG2", SubNfState.CREATED.value)
+        measurement_grp_rel = (NfMeasureGroupRelationalModel.query.filter(
+            NfMeasureGroupRelationalModel.measurement_grp_name == 'MG2',
+            NfMeasureGroupRelationalModel.nf_name == 'pnf_test2').one_or_none())
+        self.assertIsNotNone(measurement_grp_rel)
+        self.assertEqual(measurement_grp_rel.nf_measure_grp_status, 'CREATED')
+        network_function = (NetworkFunctionModel.query.filter(
+            NetworkFunctionModel.nf_name == 'pnf_test2').one_or_none())
+        self.assertIsNotNone(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, 'UNLOCKED')
index 807806f..a0f3297 100644 (file)
@@ -21,13 +21,14 @@ import os
 from unittest.mock import patch, MagicMock
 from mod.api.db_models import SubscriptionModel, MeasurementGroupModel, \
     NfMeasureGroupRelationalModel, NetworkFunctionModel, NfSubRelationalModel, \
-    convert_db_string_to_list
+    convert_db_string_to_list, NetworkFunctionFilterModel
 from mod.network_function import NetworkFunctionFilter
 from mod.subscription import SubNfState
-from mod import aai_client
-from mod.api.custom_exception import DuplicateDataException, InvalidDataException
+from mod import aai_client, db
+from mod.api.custom_exception import DuplicateDataException, InvalidDataException, \
+    DataConflictException
 from mod.pmsh_config import AppConfig
-from tests.base_setup import BaseClassSetup
+from tests.base_setup import BaseClassSetup, create_subscription_data
 from mod.api.services import subscription_service, nf_service, measurement_group_service
 from tests.base_setup import create_multiple_subscription_data
 
@@ -184,11 +185,10 @@ class SubscriptionServiceTestCase(BaseClassSetup):
                                                 'msrmt_grp_name', 'UNLOCKED',
                                                 15, 'pm.xml', [], [])
         measurement2 = self.create_measurement_grp(measurement_grp, 'meas2', 'UNLOCKED')
-        measurement3 = self.create_measurement_grp(measurement_grp, 'meas3', 'LOCKED')
-        measurement_grps = [measurement_grp, measurement2, measurement3]
+        unlocked_msgs = [measurement_grp, measurement2]
         mock_filter_call.return_value = NetworkFunctionFilter(**subscription["nfFilter"])
         filtered_nfs = nf_service.capture_filtered_nfs(subscription["subscriptionName"])
-        subscription_service.apply_measurement_grp_to_nfs(filtered_nfs, measurement_grps)
+        subscription_service.apply_measurement_grp_to_nfs(filtered_nfs, unlocked_msgs)
         # 2 measurement group with 2 nfs each contribute 4 calls
         self.assertEqual(mock_apply_nf.call_count, 4)
 
@@ -377,3 +377,168 @@ class SubscriptionServiceTestCase(BaseClassSetup):
     def test_get_subscriptions_list_empty(self):
         subs = subscription_service.get_subscriptions_list()
         self.assertEqual(subs, [])
+
+    @patch('mod.api.services.nf_service.save_nf_filter_update')
+    @patch('mod.api.services.subscription_service.is_duplicate_filter',
+           MagicMock(return_value=False))
+    @patch('mod.api.services.subscription_service.save_nf_filter', MagicMock(return_value=None))
+    @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_filter(self, mock_filter_call, mock_model_aai, mock_aai, mock_update_filter):
+        mock_aai.return_value = json.loads(self.aai_response_data)
+        mock_model_aai.return_value = json.loads(self.good_model_info)
+        mock_update_filter.return_value = None
+        subscription = self.create_test_subs('sub_01', 'msg_01')
+        subscription = json.loads(subscription)['subscription']
+        nf_filter = subscription['nfFilter']
+        mock_filter_call.return_value = NetworkFunctionFilter(**nf_filter)
+        subscription_service.create_subscription(subscription)
+        subscription_service.update_filter('sub_01', nf_filter)
+        self.assertTrue(mock_update_filter.called)
+
+    @patch('mod.api.services.nf_service.save_nf_filter_update')
+    @patch('mod.api.services.subscription_service.is_duplicate_filter',
+           MagicMock(return_value=False))
+    @patch('mod.api.services.subscription_service.save_nf_filter', MagicMock(return_value=None))
+    @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_filter_with_new_del_nfs(self, mock_filter_call, mock_model_aai, mock_aai,
+                                            mock_update_filter):
+        mock_aai.return_value = json.loads(self.aai_response_data)
+        mock_model_aai.return_value = json.loads(self.good_model_info)
+        mock_update_filter.return_value = None
+        subscription = self.create_test_subs('sub_01', 'msg_01')
+        subscription = json.loads(subscription)['subscription']
+        nf_filter = subscription['nfFilter']
+        mock_filter_call.return_value = NetworkFunctionFilter(**nf_filter)
+        subscription_service.create_subscription(subscription)
+        # Check existing network functions
+        meas_group_nfs = db.session.query(NfMeasureGroupRelationalModel).filter(
+            NfMeasureGroupRelationalModel.measurement_grp_name == 'msg_01') \
+            .all()
+        self.assertEqual(len(meas_group_nfs), 2)
+        self.assertEqual(meas_group_nfs[0].nf_name, 'pnf201')
+        self.assertEqual(meas_group_nfs[0].nf_measure_grp_status,
+                         SubNfState.PENDING_CREATE.value)
+        self.assertEqual(meas_group_nfs[1].nf_name, 'pnf_33_ericsson')
+        self.assertEqual(meas_group_nfs[1].nf_measure_grp_status,
+                         SubNfState.PENDING_CREATE.value)
+        meas_grp = measurement_group_service.query_meas_group_by_name('sub_01', 'msg_01')
+        self.assertEqual(meas_grp.administrative_state, 'UNLOCKED')
+        # Creating test data for update filter function
+        aai_response = self.aai_response_data.replace('pnf201', 'xnf111')
+        mock_aai.return_value = json.loads(aai_response)
+        nf_filter['nfNames'] = ["^vnf.*", "^xnf.*"]
+        mock_filter_call.return_value = NetworkFunctionFilter(**nf_filter)
+        subscription_service.update_filter('sub_01', nf_filter)
+        self.assertTrue(mock_update_filter.called)
+        # Check updated network functions after filter change
+        meas_group_nfs = db.session.query(NfMeasureGroupRelationalModel).filter(
+            NfMeasureGroupRelationalModel.measurement_grp_name == 'msg_01') \
+            .all()
+        self.assertEqual(meas_group_nfs[0].nf_name, 'pnf201')
+        self.assertEqual(meas_group_nfs[0].nf_measure_grp_status,
+                         SubNfState.PENDING_DELETE.value)
+        self.assertEqual(meas_group_nfs[1].nf_name, 'pnf_33_ericsson')
+        self.assertEqual(meas_group_nfs[1].nf_measure_grp_status,
+                         SubNfState.PENDING_DELETE.value)
+        self.assertEqual(meas_group_nfs[2].nf_name, 'xnf111')
+        self.assertEqual(meas_group_nfs[2].nf_measure_grp_status,
+                         SubNfState.PENDING_CREATE.value)
+        meas_grp = measurement_group_service.query_meas_group_by_name('sub_01', 'msg_01')
+        self.assertEqual(meas_grp.administrative_state, 'FILTERING')
+
+    def test_update_filter_locking(self):
+        sub = create_subscription_data('sub')
+        sub.measurement_groups[1].administrative_state = 'LOCKING'
+        db.session.add(sub)
+        try:
+            subscription_service.update_filter('sub', json.loads('{"nfNames": "^pnf.*"}'))
+        except DataConflictException as conflictEx:
+            self.assertEqual(conflictEx.args[0],
+                             'Cannot update filter as subscription: sub is under transitioning'
+                             ' state for the following measurement groups: [subscription_name:'
+                             ' sub, measurement_group_name: MG2, administrative_state: LOCKING,'
+                             ' file_based_gp: 15, file_location: /pm/pm.xml, measurement_type: '
+                             '[{ "measurementType": "countera" }, { "measurementType": '
+                             '"counterb" }], managed_object_dns_basic: [{ "DN":"dna"},'
+                             '{"DN":"dnb"}]]')
+
+    def test_update_filter_filtering(self):
+        sub = create_subscription_data('sub')
+        sub.measurement_groups[1].administrative_state = 'FILTERING'
+        db.session.add(sub)
+        try:
+            subscription_service.update_filter('sub', json.loads('{"nfNames": "^pnf.*"}'))
+        except DataConflictException as conflictEx:
+            self.assertEqual(conflictEx.args[0],
+                             'Cannot update filter as subscription: sub is under transitioning'
+                             ' state for the following measurement groups: [subscription_name:'
+                             ' sub, measurement_group_name: MG2, administrative_state: FILTERING,'
+                             ' file_based_gp: 15, file_location: /pm/pm.xml, measurement_type: ['
+                             '{ "measurementType": "countera" }, { "measurementType": "counterb" '
+                             '}], managed_object_dns_basic: [{ "DN":"dna"},{"DN":"dnb"}]]')
+
+    def test_update_filter_invalid_request(self):
+        try:
+            subscription_service.update_filter("sub3", json.loads('{"nfNames": "^pnf.*"}'))
+        except InvalidDataException as invalidEx:
+            self.assertEqual(invalidEx.args[0],
+                             "Requested subscription is not available with sub name: sub3 "
+                             "for nf filter update")
+
+    @patch('mod.api.services.nf_service.save_nf_filter_update')
+    @patch('mod.api.services.subscription_service.is_duplicate_filter',
+           MagicMock(return_value=True))
+    @patch('mod.api.services.subscription_service.save_nf_filter', MagicMock(return_value=None))
+    @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_filter_invalid_duplicate_request(self, mock_filter_call, mock_model_aai,
+                                                     mock_aai, mock_update_filter):
+        try:
+            mock_aai.return_value = json.loads(self.aai_response_data)
+            mock_model_aai.return_value = json.loads(self.good_model_info)
+            mock_update_filter.return_value = None
+            subscription = self.create_test_subs('sub_01', 'msg_01')
+            subscription = json.loads(subscription)['subscription']
+            nf_filter = subscription['nfFilter']
+            mock_filter_call.return_value = NetworkFunctionFilter(**nf_filter)
+            subscription_service.create_subscription(subscription)
+            subscription_service.update_filter("sub_01", json.loads('{"nfNames": "^pnf.*"}'))
+        except InvalidDataException as invalidEx:
+            self.assertEqual(invalidEx.args[0],
+                             "Duplicate nf filter update requested for subscription "
+                             "with sub name: sub_01")
+
+    def test_is_duplicate_filter_true(self):
+        subscription = self.create_test_subs('sub_01', 'msg_01')
+        subscription = json.loads(subscription)['subscription']
+        nf_filter = subscription['nfFilter']
+        db_network_filter = NetworkFunctionFilterModel('sub_01',
+                                                       '{^pnf.*,^vnf.*}',
+                                                       '{8lk4578-d396-4efb-af02-6b83499b12f8,'
+                                                       '687kj45-d396-4efb-af02-6b83499b12f8,'
+                                                       '597b524-d396-4efb-af02-6b83499b12f8}',
+                                                       '{e80a6ae3-cafd-4d24-850d-e14c084a5ca9}',
+                                                       '{PNF102}')
+        similar = subscription_service.is_duplicate_filter(nf_filter, db_network_filter)
+        self.assertTrue(similar)
+
+    def test_is_duplicate_filter_false(self):
+        subscription = self.create_test_subs('sub_01', 'msg_01')
+        subscription = json.loads(subscription)['subscription']
+        nf_filter = subscription['nfFilter']
+        db_network_filter = NetworkFunctionFilterModel('sub_01',
+                                                       '{^pnf.*,^vnf.*}',
+                                                       '{8lk4578-d396-4efb-af02-6b83499b12f8,'
+                                                       '687kj45-d396-4efb-af02-6b83499b12f8}',
+                                                       '{e80a6ae3-cafd-4d24-850d-e14c084a5ca9}',
+                                                       '{PNF102}')
+        similar = subscription_service.is_duplicate_filter(nf_filter, db_network_filter)
+        self.assertFalse(similar)
index 42c52c0..7b0a8b1 100755 (executable)
@@ -23,7 +23,7 @@ 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
+    delete_meas_group_by_name, 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
@@ -366,3 +366,33 @@ class ControllerTestCase(BaseClassSetup):
         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')
+
+    @patch('mod.api.services.subscription_service.update_filter', MagicMock(return_value=None))
+    def test_put_nf_filter(self):
+        response = put_nf_filter('sub1', json.loads('{"nfNames": ["^pnf.*", "^vnf.*"]}'))
+        self.assertEqual(response[0], 'Successfully updated network function Filter')
+        self.assertEqual(response[1], HTTPStatus.OK.value)
+
+    @patch('mod.api.services.subscription_service.update_filter',
+           MagicMock(side_effect=InvalidDataException('Bad request')))
+    def test_put_nf_filter_api_invalid_data_exception(self):
+        error, status_code = put_nf_filter('sub1',
+                                           json.loads('{"nfNames": ["^pnf.*", "^vnf.*"]}'))
+        self.assertEqual(status_code, HTTPStatus.BAD_REQUEST.value)
+        self.assertEqual(error, 'Bad request')
+
+    @patch('mod.api.services.subscription_service.update_filter',
+           MagicMock(side_effect=DataConflictException('Data conflict')))
+    def test_put_nf_filter_api_data_conflict_exception(self):
+        error, status_code = put_nf_filter('sub1',
+                                           json.loads('{"nfNames": ["^pnf.*", "^vnf.*"]}'))
+        self.assertEqual(status_code, HTTPStatus.CONFLICT.value)
+        self.assertEqual(error, 'Data conflict')
+
+    @patch('mod.api.services.subscription_service.update_filter',
+           MagicMock(side_effect=Exception('Server Error')))
+    def test_put_nf_filter_api_exception(self):
+        error, status_code = put_nf_filter('sub1', json.loads('{"nfNames": ["^pnf.*", "^vnf.*"]}'))
+        self.assertEqual(status_code, HTTPStatus.INTERNAL_SERVER_ERROR.value)
+        self.assertEqual(error, 'Update nf filter request was not processed for sub name: sub1 '
+                                'due to Exception : Server Error')
index 3e6abf9..d5ae5ce 100644 (file)
@@ -1,5 +1,5 @@
 # ============LICENSE_START===================================================
-#  Copyright (C) 2019-2021 Nordix Foundation.
+#  Copyright (C) 2019-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.
@@ -18,6 +18,7 @@
 from unittest.mock import patch, MagicMock
 
 from mod import db
+from mod.api.services import measurement_group_service
 from mod.network_function import NetworkFunction
 from mod.policy_response_handler import PolicyResponseHandler, policy_response_handle_functions
 from mod.subscription import AdministrativeState, SubNfState
@@ -99,6 +100,40 @@ class PolicyResponseHandlerTest(BaseClassSetup):
                 measurement_group_name='msr_grp_name',
                 status=SubNfState.CREATED.value, nf_name=self.nf.nf_name)
 
+    @patch('mod.api.services.measurement_group_service.update_measurement_group_nf_status')
+    def test_handle_response_unlocked_success_filtering(self, mock_update_sub_nf):
+        with patch.dict(policy_response_handle_functions,
+                        {AdministrativeState.UNLOCKED.value: {'success': mock_update_sub_nf}}):
+            sub = create_subscription_data('sub')
+            db.session.add(sub)
+            measurement_group_service.apply_nf_status_to_measurement_group(
+                self.nf.nf_name, "MG2", SubNfState.PENDING_CREATE.value)
+            db.session.commit()
+            self.policy_response_handler._handle_response(
+                'MG2',
+                AdministrativeState.FILTERING.value,
+                self.nf.nf_name, 'success')
+            mock_update_sub_nf.assert_called_with(
+                measurement_group_name='MG2',
+                status=SubNfState.CREATED.value, nf_name=self.nf.nf_name)
+
+    @patch('mod.api.services.measurement_group_service.update_measurement_group_nf_status')
+    def test_handle_response_locking_success_filtering(self, mock_update_sub_nf):
+        with patch.dict(policy_response_handle_functions,
+                        {AdministrativeState.LOCKING.value: {'success': mock_update_sub_nf}}):
+            sub = create_subscription_data('sub')
+            db.session.add(sub)
+            measurement_group_service.apply_nf_status_to_measurement_group(
+                self.nf.nf_name, "MG2", SubNfState.PENDING_DELETE.value)
+            db.session.commit()
+            self.policy_response_handler._handle_response(
+                'MG2',
+                AdministrativeState.FILTERING.value,
+                self.nf.nf_name, 'success')
+            mock_update_sub_nf.assert_called_with(
+                measurement_group_name='MG2',
+                status=SubNfState.DELETED.value, nf_name=self.nf.nf_name)
+
     @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,
@@ -111,6 +146,40 @@ class PolicyResponseHandlerTest(BaseClassSetup):
                 measurement_group_name='msr_grp_name',
                 status=SubNfState.CREATE_FAILED.value, nf_name=self.nf.nf_name)
 
+    @patch('mod.api.services.measurement_group_service.update_measurement_group_nf_status')
+    def test_handle_response_create_failed_filtering(self, mock_update_sub_nf):
+        with patch.dict(policy_response_handle_functions,
+                        {AdministrativeState.UNLOCKED.value: {'failed': mock_update_sub_nf}}):
+            sub = create_subscription_data('sub')
+            db.session.add(sub)
+            measurement_group_service.apply_nf_status_to_measurement_group(
+                self.nf.nf_name, "MG2", SubNfState.PENDING_CREATE.value)
+            db.session.commit()
+            self.policy_response_handler._handle_response(
+                'MG2',
+                AdministrativeState.FILTERING.value,
+                self.nf.nf_name, 'failed')
+            mock_update_sub_nf.assert_called_with(
+                measurement_group_name='MG2',
+                status=SubNfState.CREATE_FAILED.value, nf_name=self.nf.nf_name)
+
+    @patch('mod.api.services.measurement_group_service.update_measurement_group_nf_status')
+    def test_handle_response_delete_failed_filtering(self, mock_update_sub_nf):
+        with patch.dict(policy_response_handle_functions,
+                        {AdministrativeState.LOCKING.value: {'failed': mock_update_sub_nf}}):
+            sub = create_subscription_data('sub')
+            db.session.add(sub)
+            measurement_group_service.apply_nf_status_to_measurement_group(
+                self.nf.nf_name, "MG2", SubNfState.PENDING_DELETE.value)
+            db.session.commit()
+            self.policy_response_handler._handle_response(
+                'MG2',
+                AdministrativeState.FILTERING.value,
+                self.nf.nf_name, 'failed')
+            mock_update_sub_nf.assert_called_with(
+                measurement_group_name='MG2',
+                status=SubNfState.DELETE_FAILED.value, nf_name=self.nf.nf_name)
+
     def test_handle_response_exception(self):
         self.assertRaises(Exception, self.policy_response_handler._handle_response, 'sub1',
                           'wrong_state', 'nf1', 'wrong_message')
index 3c5fba7..3ad2137 100644 (file)
@@ -1,6 +1,6 @@
 major=2
-minor=1
-patch=1
+minor=2
+patch=0
 base_version=${major}.${minor}.${patch}
 release_version=${base_version}
 snapshot_version=${base_version}-SNAPSHOT