[DCAEGEN2] PMSH Create Subscription public API

Issue-ID: DCAEGEN2-2819
Change-Id: I80636be25dc4f7b1c5ce7470c7a38c010cb339a1
Signed-off-by: SagarS <sagar.shetty@est.tech>
diff --git a/components/pm-subscription-handler/Changelog.md b/components/pm-subscription-handler/Changelog.md
index f98d2b8..eff8419 100755
--- a/components/pm-subscription-handler/Changelog.md
+++ b/components/pm-subscription-handler/Changelog.md
@@ -9,6 +9,7 @@
 ### Changed
 * Updated PMSH app configuration, simplified existing config (DCAEGEN2-2814)
 * Created Schema definitions in swagger file according to the new structure (DCAEGEN2-2889)
+* Implemented Create Subscription public API (DCAEGEN2-2819)
 
 ## [1.3.2]
 ### Changed
diff --git a/components/pm-subscription-handler/pmsh_service/mod/aai_client.py b/components/pm-subscription-handler/pmsh_service/mod/aai_client.py
index 39adba4..d2aeb0f 100755
--- a/components/pm-subscription-handler/pmsh_service/mod/aai_client.py
+++ b/components/pm-subscription-handler/pmsh_service/mod/aai_client.py
@@ -17,22 +17,20 @@
 # ============LICENSE_END=====================================================
 import json
 from os import environ
-
 import requests
 from requests.auth import HTTPBasicAuth
-
 import mod.network_function
 import mod.pmsh_utils
 from mod import logger
 
 
-def get_pmsh_nfs_from_aai(app_conf):
+def get_pmsh_nfs_from_aai(app_conf, nf_filter):
     """
     Returns the Network Functions from AAI related to the Subscription.
 
     Args:
         app_conf (AppConfig): the AppConfig object.
-
+        nf_filter (NetworkFunctionFilter): the filter to apply on nf from aai
     Returns:
         NetworkFunctions (list): list of NetworkFunctions.
 
@@ -41,7 +39,7 @@
     """
     aai_nf_data = _get_all_aai_nf_data(app_conf)
     if aai_nf_data:
-        nfs = _filter_nf_data(aai_nf_data, app_conf)
+        nfs = _filter_nf_data(aai_nf_data, app_conf, nf_filter)
     else:
         raise RuntimeError('Failed to get data from AAI')
     return nfs
@@ -114,14 +112,14 @@
             'RequestID': kwargs['request_id']}
 
 
-def _filter_nf_data(nf_data, app_conf):
+def _filter_nf_data(nf_data, app_conf, nf_filter):
     """
     Returns a list of filtered NetworkFunctions using the nf_filter.
 
     Args:
         nf_data (dict): the nf json data from AAI.
         app_conf (AppConfig): the AppConfig object.
-
+        nf_filter (NetworkFunctionFilter): filter data to apply on network functions
     Returns:
         NetworkFunction (list): a list of filtered NetworkFunction Objects.
 
@@ -142,7 +140,7 @@
                 model_version_id=nf['properties'].get('model-version-id'))
             if not new_nf.set_nf_model_params(app_conf):
                 continue
-            if app_conf.nf_filter.is_nf_in_filter(new_nf):
+            if nf_filter.is_nf_in_filter(new_nf):
                 nf_list.append(new_nf)
     except KeyError as e:
         logger.error(f'Failed to parse AAI data: {e}', exc_info=True)
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 21d29ca..8af6c77 100755
--- a/components/pm-subscription-handler/pmsh_service/mod/api/controller.py
+++ b/components/pm-subscription-handler/pmsh_service/mod/api/controller.py
@@ -1,5 +1,5 @@
 # ============LICENSE_START===================================================
-#  Copyright (C) 2019-2020 Nordix Foundation.
+#  Copyright (C) 2019-2021 Nordix Foundation.
 # ============================================================================
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -17,6 +17,11 @@
 # ============LICENSE_END=====================================================
 
 from mod.subscription import Subscription
+from http import HTTPStatus
+from mod import logger
+from mod.api.services import subscription_service
+from connexion import NoContent
+from mod.api.custom_exception import InvalidDataException, DuplicateDataException
 
 
 def status():
@@ -40,3 +45,34 @@
     """
     subs_dict = [s.serialize() for s in Subscription.get_all()]
     return subs_dict
+
+
+def post_subscription(body):
+    """
+    Creates a subscription
+
+    Args:
+        body (dict): subscription request body to save.
+
+    Returns:
+        Success : NoContent, 201
+        Invalid Data : Invalid message, 400
+        Duplicate Data : Duplicate field detail, 409
+
+    Raises:
+        Error: If anything fails in the server.
+    """
+    response = NoContent, HTTPStatus.CREATED.value
+    try:
+        subscription_service.create_subscription(body['subscription'])
+    except DuplicateDataException as e:
+        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
+    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
+    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
new file mode 100644
index 0000000..606d500
--- /dev/null
+++ b/components/pm-subscription-handler/pmsh_service/mod/api/custom_exception.py
@@ -0,0 +1,38 @@
+# ============LICENSE_START===================================================
+#  Copyright (C) 2021 Nordix Foundation.
+# ============================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+# ============LICENSE_END=====================================================
+
+class InvalidDataException(Exception):
+    """Exception raised for invalid inputs.
+
+    Attributes:
+        message -- detail on invalid data
+    """
+
+    def __init__(self, invalid_message):
+        self.invalid_message = invalid_message
+
+
+class DuplicateDataException(Exception):
+    """Exception raised for duplicate inputs.
+
+    Attributes:
+        message -- detail on duplicate field
+    """
+
+    def __init__(self, duplicate_field_info):
+        self.duplicate_field_info = duplicate_field_info
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 5c87fa5..2b340e2 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
@@ -179,9 +179,11 @@
                f'model_version_ids: {self.model_version_ids}, model_names: {self.model_names}'
 
     def serialize(self):
-        return {'subscription_name': self.subscription_name, 'nf_names': self.nf_names,
-                'model_invariant_ids': self.model_invariant_ids,
-                'model_version_ids': self.model_version_ids, 'model_names': self.model_names}
+        return {'subscriptionName': self.subscription_name,
+                'nfNames': convert_db_string_to_list(self.nf_names),
+                'modelInvariantIDs': convert_db_string_to_list(self.model_invariant_ids),
+                'modelVersionIDs': convert_db_string_to_list(self.model_version_ids),
+                'modelNames': convert_db_string_to_list(self.model_names)}
 
 
 class MeasurementGroupModel(db.Model):
@@ -256,3 +258,16 @@
     def __repr__(self):
         return f'measurement_grp_name: {self.measurement_grp_name}, ' \
             f'nf_name: {self.nf_name}, nf_measure_grp_status: {self.nf_measure_grp_status}'
+
+
+def convert_db_string_to_list(db_string):
+    """
+    Converts a db string to array and
+    removes empty strings
+    Args:
+        db_string (string): The db string to convert into an array
+    Returns:
+        list[string]: converted list of strings else empty
+    """
+    array_format = db_string.strip('{}').split(',')
+    return [x for x in array_format if x.strip() != ""]
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 2a6137c..11cea4e 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
@@ -21,6 +21,8 @@
   title: PM Subscription Handler Service
   version: "2.0.0"
   description: PM subscription handler enables control of performance management jobs on network functions in ONAP
+consumes:
+  - "application/json"
 produces:
   - "application/json"
 basePath: "/"
@@ -89,6 +91,27 @@
         503:
           description: the pmsh service is unavailable
 
+  /subscription:
+    post:
+      tags:
+        - "Subscription"
+      description: >-
+        Create a PM Subscription
+      operationId: mod.api.controller.post_subscription
+      parameters:
+        - in: "body"
+          name: "body"
+          required: true
+          schema:
+            $ref: "#/definitions/subscription"
+      responses:
+        201:
+          description: successfully created PM Subscription
+        409:
+          description: Duplicate data
+        400:
+          description: Invalid input
+
 definitions:
   subscription:
     type: object
@@ -119,29 +142,30 @@
 
   nfFilter:
     type: object
-    minProperties: 1
+    description: "At least one valid filter value within nfFilter is required"
     additionalProperties: false
     properties:
       nfNames:
         type: array
-        minItems: 1
         items:
           type: string
       modelInvariantIDs:
         type: array
-        minItems: 1
         items:
           type: string
       modelVersionIDs:
         type: array
-        minItems: 1
         items:
           type: string
       modelNames:
         type: array
-        minItems: 1
         items:
           type: string
+    required:
+      - nfNames
+      - modelInvariantIDs
+      - modelVersionIDs
+      - modelNames
 
   measurementGroup:
     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
new file mode 100644
index 0000000..329dc85
--- /dev/null
+++ b/components/pm-subscription-handler/pmsh_service/mod/api/services/measurement_group_service.py
@@ -0,0 +1,87 @@
+# ============LICENSE_START===================================================
+#  Copyright (C) 2021 Nordix Foundation.
+# ============================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+# ============LICENSE_END=====================================================
+
+from mod.api.db_models import MeasurementGroupModel, NfMeasureGroupRelationalModel
+from mod import db, logger
+from mod.subscription import SubNfState
+from mod.api.services import nf_service
+from mod.pmsh_config import MRTopic, AppConfig
+
+
+def save_measurement_group(measurement_group, subscription_name):
+    """
+    Saves the measurement_group data request
+
+    Args:
+        measurement_group (dict) : measurement group to save
+        subscription_name (string) : subscription name to associate with measurement group.
+
+    Returns:
+        MeasurementGroupModel : measurement group saved in the database
+    """
+    logger.info(f'Saving measurement group for subscription request: {subscription_name}')
+    new_measurement_group = MeasurementGroupModel(
+        subscription_name=subscription_name,
+        measurement_group_name=measurement_group['measurementGroupName'],
+        administrative_state=measurement_group['administrativeState'],
+        file_based_gp=measurement_group['fileBasedGP'],
+        file_location=measurement_group['fileLocation'],
+        measurement_type=measurement_group['measurementTypes'],
+        managed_object_dns_basic=measurement_group['managedObjectDNsBasic'])
+    db.session.add(new_measurement_group)
+    return new_measurement_group
+
+
+def apply_nf_to_measgroup(nf_name, measurement_group_name):
+    """
+    Associate and saves the measurement group with Network function
+
+    Args:
+        nf_name (string): Network function name.
+        measurement_group_name (string): Measurement group name
+    """
+    new_nf_measure_grp_rel = NfMeasureGroupRelationalModel(
+        measurement_grp_name=measurement_group_name,
+        nf_name=nf_name,
+        nf_measure_grp_status=SubNfState.PENDING_CREATE.value
+    )
+    db.session.add(new_nf_measure_grp_rel)
+
+
+def publish_measurement_group(subscription_name, measurement_group, nf):
+    """
+    Publishes an event for measurement group against nfs to MR
+
+    Args:
+        subscription_name (string): Subscription name to publish against nf
+        measurement_group (MeasurementGroupModel): Measurement group to publish
+        nf (NetworkFunction): Network function to publish.
+   """
+    event_body = nf_service.create_nf_event_body(nf, 'CREATE')
+    event_body['subscription'] = {
+        "administrativeState": measurement_group.administrative_state,
+        "subscriptionName": subscription_name,
+        "fileBasedGP": measurement_group.file_based_gp,
+        "fileLocation": measurement_group.file_location,
+        "measurementGroup": {
+            "measurementGroupName": measurement_group.measurement_group_name,
+            "measurementTypes": measurement_group.measurement_type,
+            "managedObjectDNsBasic": measurement_group.managed_object_dns_basic
+        }
+    }
+    AppConfig.get_instance().publish_to_topic(MRTopic.POLICY_PM_PUBLISHER.value, event_body)
diff --git a/components/pm-subscription-handler/pmsh_service/mod/api/services/nf_service.py b/components/pm-subscription-handler/pmsh_service/mod/api/services/nf_service.py
new file mode 100644
index 0000000..1fca766
--- /dev/null
+++ b/components/pm-subscription-handler/pmsh_service/mod/api/services/nf_service.py
@@ -0,0 +1,75 @@
+# ============LICENSE_START===================================================
+#  Copyright (C) 2021 Nordix Foundation.
+# ============================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+# ============LICENSE_END=====================================================
+
+from mod import db, aai_client, logger
+from mod.api.db_models import NetworkFunctionModel
+from mod.pmsh_config import AppConfig
+from mod.network_function import NetworkFunctionFilter
+
+
+def capture_filtered_nfs(sub_name):
+    """
+    Retrieves network functions from AAI client and
+    returns a list of filtered NetworkFunctions using the Filter
+
+    Args:
+        sub_name (string): The name of subscription inorder to perform filtering
+    Returns:
+        list[NetworkFunction]: a list of filtered NetworkFunction Objects.
+    """
+    logger.info(f'Getting filtered nfs for subscription: {sub_name}')
+    nf_filter = NetworkFunctionFilter.get_network_function_filter(sub_name)
+    return aai_client.get_pmsh_nfs_from_aai(AppConfig.get_instance(), nf_filter)
+
+
+def create_nf_event_body(nf, change_type):
+    """
+    Creates a network function event body to publish on MR
+
+    Args:
+        nf (NetworkFunction): the Network function to include in the event.
+        change_type (string): define the change type to be applied on node
+    Returns:
+        dict: network function event body to publish on MR.
+    """
+    app_conf = AppConfig.get_instance()
+    return {'nfName': nf.nf_name,
+            'ipAddress': nf.ipv4_address if nf.ipv6_address in (None, '')
+            else nf.ipv6_address,
+            'blueprintName': nf.sdnc_model_name,
+            'blueprintVersion': nf.sdnc_model_version,
+            'policyName': app_conf.operational_policy_name,
+            'changeType': change_type,
+            'closedLoopControlName': app_conf.control_loop_name}
+
+
+def save_nf(nf):
+    """
+    Saves the network function request to the db
+    Args:
+        nf (NetworkFunction) : requested network function to save
+    """
+    network_function = NetworkFunctionModel(nf_name=nf.nf_name,
+                                            ipv4_address=nf.ipv4_address,
+                                            ipv6_address=nf.ipv6_address,
+                                            model_invariant_id=nf.model_invariant_id,
+                                            model_version_id=nf.model_version_id,
+                                            model_name=nf.model_name,
+                                            sdnc_model_name=nf.sdnc_model_name,
+                                            sdnc_model_version=nf.sdnc_model_version)
+    db.session.add(network_function)
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
new file mode 100644
index 0000000..1485beb
--- /dev/null
+++ b/components/pm-subscription-handler/pmsh_service/mod/api/services/subscription_service.py
@@ -0,0 +1,275 @@
+# ============LICENSE_START===================================================
+#  Copyright (C) 2021 Nordix Foundation.
+# ============================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+# ============LICENSE_END=====================================================
+
+from mod import db, logger
+from mod.api.db_models import SubscriptionModel, NfSubRelationalModel, \
+    NetworkFunctionFilterModel, NetworkFunctionModel
+from mod.api.services import measurement_group_service, nf_service
+from mod.api.custom_exception import InvalidDataException, DuplicateDataException
+from mod.subscription import AdministrativeState
+from sqlalchemy.exc import IntegrityError
+
+
+def create_subscription(subscription):
+    """
+    Creates a subscription
+
+    Args:
+        subscription (dict): subscription to save.
+
+    Raises:
+        DuplicateDataException: contains details on duplicate fields
+        Exception: contains runtime error details
+    """
+    logger.info(f'Initiating create subscription for: {subscription["subscriptionName"]}')
+    perform_validation(subscription)
+    try:
+        sub_name, measurement_groups = 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_name)
+        if filtered_nfs:
+            logger.info(f'Applying the filtered nfs for subscription: {sub_name}')
+            save_filtered_nfs(filtered_nfs)
+            apply_subscription_to_nfs(filtered_nfs, sub_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_name, filtered_nfs, unlocked_msmt_groups)
+            else:
+                logger.error(f'All measurement groups are locked for subscription: {sub_name}, '
+                             f'please verify/check measurement groups.')
+        else:
+            logger.error(f'No network functions found for subscription: {sub_name}, '
+                         f'please verify/check NetworkFunctionFilter.')
+    except IntegrityError as e:
+        db.session.rollback()
+        raise DuplicateDataException(f'DB Integrity issue encountered: {e.orig.args[0]}')
+    except Exception as e:
+        db.session.rollback()
+        raise e
+    finally:
+        db.session.remove()
+
+
+def publish_measurement_grp_to_nfs(subscription_name, filtered_nfs, measurement_groups):
+    """
+    Publishes an event for measurement groups against nfs
+
+    Args:
+        subscription_name (string): subscription name against nfs
+        filtered_nfs (list[NetworkFunction])): list of filtered network functions
+        measurement_groups (list[MeasurementGroupModel]): list of unlocked measurement group
+    """
+    for measurement_group in measurement_groups:
+        for nf in filtered_nfs:
+            try:
+                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(
+                    subscription_name, measurement_group, nf)
+            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'{subscription_name} with error: {ex}')
+
+
+def save_filtered_nfs(filtered_nfs):
+    """
+    Saves a network function
+
+    Args:
+        filtered_nfs (list[NetworkFunction]): list of filtered network functions to save.
+    """
+    pmsh_nf_names = list(nf.nf_name for nf in NetworkFunctionModel.query.all())
+    for nf in filtered_nfs:
+        if nf.nf_name not in pmsh_nf_names:
+            nf_service.save_nf(nf)
+
+
+def apply_subscription_to_nfs(filtered_nfs, subscription_name):
+    """
+    Associate and saves the subscription with Network functions
+
+    Args:
+        filtered_nfs (list[NetworkFunction]): list of filtered network functions to save.
+        subscription_name (string): subscription name to save against nfs
+    """
+    logger.info(f'Saving filtered nfs for subscription: {subscription_name}')
+    for nf in filtered_nfs:
+        new_nf_sub_rel = NfSubRelationalModel(subscription_name=subscription_name,
+                                              nf_name=nf.nf_name)
+        db.session.add(new_nf_sub_rel)
+
+
+def apply_measurement_grp_to_nfs(filtered_nfs, measurement_groups):
+    """
+    Saves measurement groups against nfs with status as PENDING_CREATE
+
+    Args:
+        filtered_nfs (list[NetworkFunction])): list of filtered network functions
+        measurement_groups (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_to_measgroup(
+                    nf.nf_name, measurement_group.measurement_group_name)
+        else:
+            logger.info(f'No nfs added as measure_grp_name: '
+                        f'{measurement_group.measurement_group_name} is LOCKED')
+    return unlocked_msmt_groups
+
+
+def check_missing_data(subscription):
+    """
+    checks if the subscription request has missing data
+
+    Args:
+        subscription (dict): subscription to validate
+
+    Raises:
+        InvalidDataException: exception containing the list of invalid data.
+    """
+    if subscription['subscriptionName'].strip() in (None, ''):
+        raise InvalidDataException("No value provided in subscription name")
+
+    for measurement_group in subscription.get('measurementGroups'):
+        measurement_group_details = measurement_group['measurementGroup']
+        if measurement_group_details['administrativeState'].strip() in (None, ''):
+            raise InvalidDataException("No value provided for administrative state")
+        if measurement_group_details['measurementGroupName'].strip() in (None, ''):
+            raise InvalidDataException("No value provided for measurement group name")
+
+
+def perform_validation(subscription):
+    """
+    validates the subscription and if invalid raises an exception
+    to indicate duplicate/invalid request
+
+    Args:
+        subscription (dict): subscription to validate
+
+    Raises:
+        DuplicateDataException: exception containing the detail on duplicate data field.
+        InvalidDataException: exception containing the detail on invalid data.
+    """
+    logger.info(f'Performing subscription validation for: {subscription["subscriptionName"]}')
+    check_missing_data(subscription)
+    logger.info(f'No missing data found for: {subscription["subscriptionName"]}')
+    check_duplicate_fields(subscription["subscriptionName"])
+    logger.info(f'No duplicate data found for: {subscription["subscriptionName"]}')
+    validate_nf_filter(subscription["nfFilter"])
+    logger.info(f'Filter data is valid for: {subscription["subscriptionName"]}')
+
+
+def save_subscription_request(subscription):
+    """
+    Saves the subscription request consisting of:
+    network function filter and measurement groups
+
+    Args:
+        subscription (dict): subscription request to be saved.
+
+    Returns:
+        string: Subscription name
+        list[MeasurementGroupModel]: list of measurement groups
+    """
+    logger.info(f'Saving subscription request for: {subscription["subscriptionName"]}')
+    sub_name = save_subscription(subscription).subscription_name
+    save_nf_filter(subscription["nfFilter"], subscription["subscriptionName"])
+    measurement_groups = []
+    for measurement_group in subscription['measurementGroups']:
+        measurement_groups.append(
+            measurement_group_service.save_measurement_group(
+                measurement_group['measurementGroup'],
+                subscription["subscriptionName"]))
+    return sub_name, measurement_groups
+
+
+def check_duplicate_fields(subscription_name):
+    """
+    validates the subscription content if already present
+    and captures duplicate fields
+
+    Args:
+        subscription_name (string): subscription name
+
+    Raises:
+        InvalidDataException: exception containing the list of invalid data.
+    """
+
+    existing_subscription = (SubscriptionModel.query.filter(
+        SubscriptionModel.subscription_name == subscription_name).one_or_none())
+    if existing_subscription is not None:
+        raise DuplicateDataException(f'subscription Name: {subscription_name} already exists.')
+
+
+def save_subscription(subscription):
+    """
+    Saves the subscription data
+
+    Args:
+        subscription (dict): subscription model to be saved.
+    """
+    subscription_model = SubscriptionModel(subscription_name=subscription["subscriptionName"],
+                                           status=AdministrativeState.LOCKED.value)
+    db.session.add(subscription_model)
+    return subscription_model
+
+
+def validate_nf_filter(nf_filter):
+    """
+    checks if the nf filter is valid
+
+    Args:
+        nf_filter (dict): nf filter to validate
+
+    Raises:
+        InvalidDataException: if no field is available in nf_filter
+    """
+    for filter_name, filter_values in nf_filter.items():
+        filter_values[:] = [x for x in filter_values if x.strip()]
+    if not [filter_name for filter_name, val in nf_filter.items() if len(val) > 0]:
+        raise InvalidDataException("At least one filter within nfFilter must not be empty")
+
+
+def save_nf_filter(nf_filter, subscription_name):
+    """
+    Saves the nf_filter data request
+
+    Args:
+        nf_filter (dict) : network function filter to save
+        subscription_name (string) : subscription name to associate with nf filter.
+    """
+    logger.info(f'Saving nf filter for subscription request: {subscription_name}')
+    new_filter = NetworkFunctionFilterModel(subscription_name=subscription_name,
+                                            nf_names=nf_filter['nfNames'],
+                                            model_invariant_ids=nf_filter['modelInvariantIDs'],
+                                            model_version_ids=nf_filter['modelVersionIDs'],
+                                            model_names=nf_filter['modelNames'])
+    db.session.add(new_filter)
diff --git a/components/pm-subscription-handler/pmsh_service/mod/network_function.py b/components/pm-subscription-handler/pmsh_service/mod/network_function.py
index f7b682d..2404c8b 100755
--- a/components/pm-subscription-handler/pmsh_service/mod/network_function.py
+++ b/components/pm-subscription-handler/pmsh_service/mod/network_function.py
@@ -19,7 +19,7 @@
 import re
 
 from mod import logger, db
-from mod.api.db_models import NetworkFunctionModel
+from mod.api.db_models import NetworkFunctionModel, NetworkFunctionFilterModel
 
 
 class NetworkFunction:
@@ -167,6 +167,20 @@
         self.model_names = kwargs.get('modelNames')
         self.regex_matcher = re.compile('|'.join(raw_regex for raw_regex in self.nf_names))
 
+    @staticmethod
+    def get_network_function_filter(sub_name):
+        """Gets the network function filter from the Database
+
+        Args:
+            sub_name (string): The name of the subscription
+
+        Returns:
+            NetworkFunctionFilter: Returns network function filter for sub_name
+        """
+        nf_filter = NetworkFunctionFilterModel.query.filter(
+            NetworkFunctionFilterModel.subscription_name == sub_name).one_or_none()
+        return NetworkFunctionFilter(**nf_filter.serialize())
+
     def is_nf_in_filter(self, nf):
         """Match the nf fields against values in Subscription.nfFilter
 
diff --git a/components/pm-subscription-handler/pmsh_service/mod/pmsh_config.py b/components/pm-subscription-handler/pmsh_service/mod/pmsh_config.py
index 9c282ab..a6fe38a 100644
--- a/components/pm-subscription-handler/pmsh_service/mod/pmsh_config.py
+++ b/components/pm-subscription-handler/pmsh_service/mod/pmsh_config.py
@@ -69,6 +69,9 @@
         self.streams_subscribes = app_config['config'].get('streams_subscribes')
         # TODO: aaf_creds variable should be removed on code cleanup
         self.aaf_creds = {'aaf_id': self.aaf_id, 'aaf_pass': self.aaf_pass}
+        # TODO: changes under discussion once resolve is confirmed will be removed
+        self.operational_policy_name = 'pmsh-operational-policy'
+        self.control_loop_name = 'pmsh-control-loop'
 
     @staticmethod
     def get_instance():
diff --git a/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py b/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py
index 6238a29..22654b8 100644
--- a/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py
+++ b/components/pm-subscription-handler/pmsh_service/mod/subscription_handler.py
@@ -78,7 +78,7 @@
                                                      self.app_conf.subscription.fileBasedGP,
                                                      self.app_conf.subscription.fileLocation,
                                                      self.app_conf.subscription.measurementGroups)
-        nfs_in_aai = aai_client.get_pmsh_nfs_from_aai(self.app_conf)
+        nfs_in_aai = aai_client.get_pmsh_nfs_from_aai(self.app_conf, self.app_conf.nf_filter)
         self.app_conf.subscription.create_subscription_on_nfs(nfs_in_aai, self.mr_pub,
                                                               self.app_conf)
         self.app_conf.subscription.update_subscription_status()
diff --git a/components/pm-subscription-handler/tests/data/create_subscription_request.json b/components/pm-subscription-handler/tests/data/create_subscription_request.json
new file mode 100644
index 0000000..0b2f86f
--- /dev/null
+++ b/components/pm-subscription-handler/tests/data/create_subscription_request.json
@@ -0,0 +1,60 @@
+{
+  "subscription": {
+    "subscriptionName": "ExtraPM-All-gNB-R2B",
+    "nfFilter": {
+      "nfNames": [
+        "^pnf.*",
+        "^vnf.*"
+      ],
+      "modelInvariantIDs": [
+        "8lk4578-d396-4efb-af02-6b83499b12f8",
+        "687kj45-d396-4efb-af02-6b83499b12f8"
+
+      ],
+      "modelVersionIDs": [
+        "e80a6ae3-cafd-4d24-850d-e14c084a5ca9"
+      ],
+      "modelNames": [
+        "PNF102"
+      ]
+    },
+    "measurementGroups": [
+      {
+        "measurementGroup": {
+            "measurementGroupName": "msrmt_grp_name",
+            "fileBasedGP":15,
+            "fileLocation":"pm.xml",
+            "administrativeState": "UNLOCKED",
+          "measurementTypes": [
+            {
+              "measurementType": "counter_a"
+            }
+          ],
+          "managedObjectDNsBasic": [
+            {
+              "DN": "string"
+            }
+          ]
+        }
+      },
+      {
+        "measurementGroup": {
+            "measurementGroupName": "msrmt_grp_name1",
+            "fileBasedGP":15,
+            "fileLocation":"pm.xml",
+            "administrativeState": "UNLOCKED",
+          "measurementTypes": [
+            {
+              "measurementType": "counter_a"
+            }
+          ],
+          "managedObjectDNsBasic": [
+            {
+              "DN": "string"
+            }
+          ]
+        }
+      }
+    ]
+  }
+}
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
new file mode 100644
index 0000000..e22b230
--- /dev/null
+++ b/components/pm-subscription-handler/tests/services/test_measurement_group_service.py
@@ -0,0 +1,113 @@
+# ============LICENSE_START===================================================
+#  Copyright (C) 2021 Nordix Foundation.
+# ============================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+# ============LICENSE_END=====================================================
+
+import json
+import os
+from unittest.mock import patch
+from mod.network_function import NetworkFunction
+from mod.pmsh_config import AppConfig
+from mod import db
+from tests.base_setup import BaseClassSetup
+from mod.api.services import measurement_group_service
+from mod.api.db_models import MeasurementGroupModel, NfMeasureGroupRelationalModel
+from mod.subscription import SubNfState
+
+
+class MeasurementGroupServiceTestCase(BaseClassSetup):
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+
+    def setUp(self):
+        super().setUp()
+        with open(os.path.join(os.path.dirname(__file__),
+                               '../data/create_subscription_request.json'), 'r') as data:
+            self.subscription_request = data.read()
+        with open(os.path.join(os.path.dirname(__file__), '../data/aai_xnfs.json'), 'r') as data:
+            self.aai_response_data = data.read()
+        with open(os.path.join(os.path.dirname(__file__), '../data/aai_model_info.json'),
+                  'r') as data:
+            self.good_model_info = data.read()
+
+    def tearDown(self):
+        super().tearDown()
+
+    @classmethod
+    def tearDownClass(cls):
+        super().tearDownClass()
+
+    @patch.object(AppConfig, 'publish_to_topic')
+    def test_publish_measurement_group(self, mock_mr):
+        super().setUpAppConf()
+        nf_1 = NetworkFunction(**{'nf_name': 'pnf_1',
+                                  'ipv4_address': '204.120.0.15',
+                                  'ipv6_address': '2001:db8:3333:4444:5555:6666:7777:8888',
+                                  'model_invariant_id': 'some_id',
+                                  'model_version_id': 'some_other_id'},
+                               sdnc_model_name='blah',
+                               sdnc_model_version=1.0, )
+        measurement_grp = MeasurementGroupModel('sub_publish',
+                                                'msg_publish', 'UNLOCKED',
+                                                15, 'pm.xml', [{"measurementType": "counter_a"}],
+                                                [{"DN": "string"}])
+        measurement_group_service.publish_measurement_group(
+            'sub_publish', measurement_grp, nf_1)
+        mock_mr.assert_called_once_with('policy_pm_publisher',
+                                        {'nfName': 'pnf_1',
+                                         'ipAddress': '2001:db8:3333:4444:5555:6666:7777:8888',
+                                         'blueprintName': 'blah',
+                                         'blueprintVersion': 1.0,
+                                         'policyName': 'pmsh-operational-policy',
+                                         'changeType': 'CREATE',
+                                         'closedLoopControlName': 'pmsh-control-loop',
+                                         'subscription':
+                                             {'administrativeState': 'UNLOCKED',
+                                              'subscriptionName': 'sub_publish',
+                                              'fileBasedGP': 15,
+                                              'fileLocation': 'pm.xml',
+                                              'measurementGroup':
+                                                  {'measurementGroupName': 'msg_publish',
+                                                   'measurementTypes':
+                                                       [{"measurementType": "counter_a"}],
+                                                   'managedObjectDNsBasic': [{"DN": "string"}]}}})
+
+    def test_save_measurement_group(self):
+        subscription = json.loads(self.subscription_request)['subscription']
+        mes_grp = subscription['measurementGroups'][0]['measurementGroup']
+        measurement_group_service.save_measurement_group(mes_grp, "ExtraPM-All-gNB-R2B")
+        db.session.commit()
+        measurement_grp = (MeasurementGroupModel.query.filter(
+            MeasurementGroupModel.measurement_group_name == mes_grp['measurementGroupName'],
+            MeasurementGroupModel.subscription_name == 'ExtraPM-All-gNB-R2B').one_or_none())
+        self.assertIsNotNone(measurement_grp)
+
+    def test_apply_nf_to_measgroup(self):
+        measurement_group_service.apply_nf_to_measgroup("pnf_test", "measure_grp_name")
+        db.session.commit()
+        measurement_grp_rel = (NfMeasureGroupRelationalModel.query.filter(
+            NfMeasureGroupRelationalModel.measurement_grp_name == 'measure_grp_name',
+            NfMeasureGroupRelationalModel.nf_name == 'pnf_test').one_or_none())
+        db.session.commit()
+        self.assertIsNotNone(measurement_grp_rel)
+        self.assertEqual(measurement_grp_rel.nf_measure_grp_status,
+                         SubNfState.PENDING_CREATE.value)
+
+    def create_test_subs(self, new_sub_name, new_msrmt_grp_name):
+        subscription = self.subscription_request.replace('ExtraPM-All-gNB-R2B', new_sub_name)
+        subscription = subscription.replace('msrmt_grp_name', new_msrmt_grp_name)
+        return subscription
diff --git a/components/pm-subscription-handler/tests/services/test_nf_service.py b/components/pm-subscription-handler/tests/services/test_nf_service.py
new file mode 100644
index 0000000..6063a8b
--- /dev/null
+++ b/components/pm-subscription-handler/tests/services/test_nf_service.py
@@ -0,0 +1,105 @@
+# ============LICENSE_START===================================================
+#  Copyright (C) 2021 Nordix Foundation.
+# ============================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+# ============LICENSE_END=====================================================
+
+import json
+import os
+from unittest.mock import patch
+from flask import current_app
+from mod.api.db_models import NetworkFunctionModel
+from mod import aai_client
+from tests.base_setup import BaseClassSetup
+from mod.api.services import nf_service
+from mod.network_function import NetworkFunctionFilter
+
+
+class NetworkFunctionServiceTestCase(BaseClassSetup):
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+
+    def setUp(self):
+        super().setUp()
+        current_app.config['app_config'] = self.app_conf
+        with open(os.path.join(os.path.dirname(__file__),
+                               '../data/create_subscription_request.json'), 'r') as data:
+            self.subscription_request = data.read()
+        with open(os.path.join(os.path.dirname(__file__), '../data/aai_xnfs.json'), 'r') as data:
+            self.aai_response_data = data.read()
+        with open(os.path.join(os.path.dirname(__file__), '../data/aai_model_info.json'),
+                  'r') as data:
+            self.good_model_info = data.read()
+
+    def tearDown(self):
+        super().tearDown()
+
+    @classmethod
+    def tearDownClass(cls):
+        super().tearDownClass()
+
+    def create_test_subs(self, new_sub_name, new_msrmt_grp_name):
+        subscription = self.subscription_request.replace('ExtraPM-All-gNB-R2B', new_sub_name)
+        subscription = subscription.replace('msrmt_grp_name', new_msrmt_grp_name)
+        return subscription
+
+    @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_capture_filtered_nfs(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)
+        subscription = json.loads(self.subscription_request)['subscription']
+        mock_filter_call.return_value = NetworkFunctionFilter(**subscription["nfFilter"])
+        filtered_nfs = nf_service.capture_filtered_nfs(subscription["subscriptionName"])
+        self.assertEqual(len(filtered_nfs), 2)
+        self.assertEqual(filtered_nfs[0].nf_name, 'pnf201')
+        self.assertEqual(filtered_nfs[1].nf_name, 'pnf_33_ericsson')
+
+    @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_create_nf_event_body(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)
+        subscription = json.loads(self.subscription_request)['subscription']
+        mock_filter_call.return_value = NetworkFunctionFilter(**subscription["nfFilter"])
+        nf = nf_service.capture_filtered_nfs(subscription["subscriptionName"])[0]
+        event_body = nf_service.create_nf_event_body(nf, 'CREATE')
+        self.assertEqual(event_body['nfName'], nf.nf_name)
+        self.assertEqual(event_body['ipAddress'], nf.ipv6_address)
+        self.assertEqual(event_body['blueprintName'], nf.sdnc_model_name)
+        self.assertEqual(event_body['blueprintVersion'], nf.sdnc_model_version)
+        self.assertEqual(event_body['policyName'],
+                         self.app_conf.operational_policy_name)
+        self.assertEqual(event_body['changeType'], 'CREATE')
+        self.assertEqual(event_body['closedLoopControlName'],
+                         self.app_conf.control_loop_name)
+
+    @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_save_nf_new_nf(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)
+        subscription = json.loads(self.subscription_request)['subscription']
+        mock_filter_call.return_value = NetworkFunctionFilter(**subscription["nfFilter"])
+        nf = nf_service.capture_filtered_nfs(subscription["subscriptionName"])[0]
+        nf.nf_name = 'newnf1'
+        nf_service.save_nf(nf)
+        network_function = NetworkFunctionModel.query.filter(
+            NetworkFunctionModel.nf_name == nf.nf_name).one_or_none()
+        self.assertIsNotNone(network_function)
diff --git a/components/pm-subscription-handler/tests/services/test_subscription_service.py b/components/pm-subscription-handler/tests/services/test_subscription_service.py
new file mode 100644
index 0000000..dca6d87
--- /dev/null
+++ b/components/pm-subscription-handler/tests/services/test_subscription_service.py
@@ -0,0 +1,353 @@
+# ============LICENSE_START===================================================
+#  Copyright (C) 2021 Nordix Foundation.
+# ============================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+# ============LICENSE_END=====================================================
+import copy
+import json
+import os
+from unittest.mock import patch, MagicMock
+from mod.api.db_models import SubscriptionModel, MeasurementGroupModel, \
+    NfMeasureGroupRelationalModel, NetworkFunctionModel, NfSubRelationalModel, \
+    convert_db_string_to_list
+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.pmsh_config import AppConfig
+from tests.base_setup import BaseClassSetup
+from mod.api.services import subscription_service, nf_service, measurement_group_service
+
+
+class SubscriptionServiceTestCase(BaseClassSetup):
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+
+    def setUp(self):
+        super().setUp()
+        with open(os.path.join(os.path.dirname(__file__),
+                               '../data/create_subscription_request.json'), 'r') as data:
+            self.subscription_request = data.read()
+        with open(os.path.join(os.path.dirname(__file__), '../data/aai_xnfs.json'), 'r') as data:
+            self.aai_response_data = data.read()
+        with open(os.path.join(os.path.dirname(__file__), '../data/aai_model_info.json'),
+                  'r') as data:
+            self.good_model_info = data.read()
+
+    def tearDown(self):
+        super().tearDown()
+
+    @classmethod
+    def tearDownClass(cls):
+        super().tearDownClass()
+
+    def create_test_subs(self, new_sub_name, new_msrmt_grp_name):
+        subscription = self.subscription_request.replace('ExtraPM-All-gNB-R2B', new_sub_name)
+        subscription = subscription.replace('msrmt_grp_name', new_msrmt_grp_name)
+        return subscription
+
+    @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_create_subscription(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)
+        subscription = self.create_test_subs('xtraPM-All-gNB-R2B-new', 'msrmt_grp_name-new')
+        subscription = json.loads(subscription)['subscription']
+        mock_filter_call.return_value = NetworkFunctionFilter(**subscription["nfFilter"])
+        subscription_service.create_subscription(subscription)
+        existing_subscription = (SubscriptionModel.query.filter(
+            SubscriptionModel.subscription_name == 'xtraPM-All-gNB-R2B-new').one_or_none())
+        self.assertIsNotNone(existing_subscription)
+        existing_measurement_grp = (MeasurementGroupModel.query.filter(
+            MeasurementGroupModel.measurement_group_name == 'msrmt_grp_name-new',
+            MeasurementGroupModel.subscription_name == 'xtraPM-All-gNB-R2B-new').one_or_none())
+        self.assertIsNotNone(existing_measurement_grp)
+        msr_grp_nf_rel = (NfMeasureGroupRelationalModel.query.filter(
+            NfMeasureGroupRelationalModel.measurement_grp_name == 'msrmt_grp_name-new')).all()
+        for pubslished_event in msr_grp_nf_rel:
+            self.assertEqual(pubslished_event.nf_measure_grp_status,
+                             SubNfState.PENDING_CREATE.value)
+
+    @patch('mod.api.services.subscription_service.save_nf_filter', MagicMock(return_value=None))
+    @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_create_subscription_service_failed_rollback(self, mock_filter_call, mock_model_aai,
+                                                         mock_aai, mock_publish):
+        mock_aai.return_value = json.loads(self.aai_response_data)
+        mock_model_aai.return_value = json.loads(self.good_model_info)
+        mock_publish.side_effect = InvalidDataException(["publish failed"])
+        subscription = self.create_test_subs('xtraPM-All-gNB-R2B-fail1', 'msrmt_grp_name-fail1')
+        subscription = json.loads(subscription)['subscription']
+        mock_filter_call.return_value = NetworkFunctionFilter(**subscription["nfFilter"])
+        try:
+            subscription_service.create_subscription(subscription)
+        except InvalidDataException as exception:
+            self.assertEqual(exception.invalid_message, ["AAI call failed"])
+
+        # Checking Rollback on publish failure with subscription and nfs captured
+        existing_subscription = (SubscriptionModel.query.filter(
+            SubscriptionModel.subscription_name == 'xtraPM-All-gNB-R2B-fail1').one_or_none())
+        self.assertIsNotNone(existing_subscription)
+        saved_nf_sub_rel = (NfSubRelationalModel.query.filter(
+            NfSubRelationalModel.subscription_name == 'xtraPM-All-gNB-R2B-fail1'))
+        self.assertIsNotNone(saved_nf_sub_rel)
+
+    @patch('mod.api.services.subscription_service.save_nf_filter', MagicMock(return_value=None))
+    @patch.object(aai_client, '_get_all_aai_nf_data')
+    @patch.object(NetworkFunctionFilter, 'get_network_function_filter')
+    def test_create_subscription_service_on_aai_failed(self, mock_filter_call, mock_aai):
+        mock_aai.side_effect = InvalidDataException(["AAI call failed"])
+        subscription = self.create_test_subs('xtraPM-All-gNB-R2B-fail', 'msrmt_grp_name-fail')
+        subscription = json.loads(subscription)['subscription']
+        mock_filter_call.return_value = NetworkFunctionFilter(**subscription["nfFilter"])
+        try:
+            subscription_service.create_subscription(subscription)
+        except InvalidDataException as exception:
+            self.assertEqual(exception.invalid_message, ["AAI call failed"])
+
+        # Checking Rollback on AAI failure with subscription request saved
+        existing_subscription = (SubscriptionModel.query.filter(
+            SubscriptionModel.subscription_name == 'xtraPM-All-gNB-R2B-fail').one_or_none())
+        self.assertIsNotNone(existing_subscription)
+
+    def test_perform_validation_existing_sub(self):
+        try:
+            subscription_service.create_subscription(json.loads(self.subscription_request)
+                                                     ['subscription'])
+        except DuplicateDataException as exception:
+            self.assertEqual(exception.duplicate_field_info,
+                             "subscription Name: ExtraPM-All-gNB-R2B already exists.")
+
+    def test_missing_measurement_grp_name(self):
+        subscription = self.create_test_subs('xtraPM-All-gNB-R2B-fail', '')
+        try:
+            subscription_service.create_subscription(json.loads(subscription)['subscription'])
+        except InvalidDataException as exception:
+            self.assertEqual(exception.invalid_message,
+                             "No value provided for measurement group name")
+
+    def test_missing_administrative_state(self):
+        subscription = json.loads(self.create_test_subs('sub-fail', 'measurement_grp_name-fail'))
+        mes_grp = subscription['subscription']['measurementGroups'][0]['measurementGroup']
+        mes_grp['administrativeState'] = ''
+        try:
+            subscription_service.create_subscription(subscription['subscription'])
+        except InvalidDataException as exception:
+            self.assertEqual(exception.invalid_message,
+                             "No value provided for administrative state")
+
+    @patch.object(subscription_service, 'save_nf_filter')
+    def test_save_subscription_request(self, mock_save_filter):
+        mock_save_filter.return_value = None
+        subscription = self.create_test_subs('xtraPM-All-gNB-R2B-new1', 'msrmt_grp_name-new1')
+        subscription_service.save_subscription_request(json.loads(subscription)['subscription'])
+        existing_subscription = (SubscriptionModel.query.filter(
+            SubscriptionModel.subscription_name == 'xtraPM-All-gNB-R2B-new1').one_or_none())
+        self.assertIsNotNone(existing_subscription)
+        self.assertTrue(mock_save_filter.called)
+        existing_measurement_grp = (MeasurementGroupModel.query.filter(
+            MeasurementGroupModel.measurement_group_name == 'msrmt_grp_name-new1',
+            MeasurementGroupModel.subscription_name == 'xtraPM-All-gNB-R2B-new1').one_or_none())
+        self.assertIsNotNone(existing_measurement_grp)
+
+    @patch.object(aai_client, '_get_all_aai_nf_data')
+    @patch.object(aai_client, 'get_aai_model_data')
+    @patch.object(measurement_group_service, 'apply_nf_to_measgroup')
+    @patch.object(NetworkFunctionFilter, 'get_network_function_filter')
+    def test_apply_measurement_grp_to_nfs(self, mock_filter_call, mock_apply_nf,
+                                          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)
+        mock_apply_nf.return_value = None
+        subscription = self.create_test_subs('xtraPM-All-gNB-R2B-new2', 'msrmt_grp_name-new2')
+        subscription = json.loads(subscription)['subscription']
+        measurement_grp = MeasurementGroupModel('subscription_name_1',
+                                                '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]
+        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)
+        # 2 measurement group with 2 nfs each contribute 4 calls
+        self.assertEqual(mock_apply_nf.call_count, 4)
+
+    @patch.object(aai_client, '_get_all_aai_nf_data')
+    @patch.object(aai_client, 'get_aai_model_data')
+    @patch.object(AppConfig, 'publish_to_topic')
+    @patch.object(NetworkFunctionFilter, 'get_network_function_filter')
+    def test_publish_measurement_grp_to_nfs(self, mock_filter_call, mock_publish,
+                                            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)
+        mock_publish.return_value = None
+        subscription = self.create_test_subs('xtraPM-All-gNB-R2B-new2', 'msrmt_grp_name-new2')
+        subscription = json.loads(subscription)['subscription']
+        measurement_grp = MeasurementGroupModel('subscription_name_1',
+                                                'msrmt_grp_name', 'UNLOCKED',
+                                                15, 'pm.xml', [], [])
+        measurement2 = self.create_measurement_grp(measurement_grp, 'meas2', 'UNLOCKED')
+        measurement3 = self.create_measurement_grp(measurement_grp, 'meas3', 'UNLOCKED')
+        measurement_grps = [measurement_grp, measurement2, measurement3]
+        mock_filter_call.return_value = NetworkFunctionFilter(**subscription["nfFilter"])
+        filtered_nfs = nf_service.capture_filtered_nfs(subscription["subscriptionName"])
+        subscription_service.publish_measurement_grp_to_nfs(
+            subscription["subscriptionName"], filtered_nfs, measurement_grps)
+        # Two unlocked measurement Group published
+        self.assertEqual(mock_publish.call_count, 6)
+
+    patch.object(aai_client, 'get_aai_model_data')
+
+    @patch.object(aai_client, '_get_all_aai_nf_data')
+    @patch.object(aai_client, 'get_aai_model_data')
+    @patch.object(AppConfig, 'publish_to_topic')
+    @patch('mod.logger.error')
+    @patch.object(NetworkFunctionFilter, 'get_network_function_filter')
+    def test_publish_measurement_grp_to_nfs_failed(self, mock_filter_call, mock_logger,
+                                                   mock_publish, 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)
+        mock_publish.side_effect = Exception('Publish failed')
+        subscription = self.create_test_subs('xtraPM-All-gNB-R2B-new2', 'msrmt_grp_name-new2')
+        subscription = json.loads(subscription)['subscription']
+        measurement_grp = MeasurementGroupModel('subscription_name_1',
+                                                '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]
+        mock_filter_call.return_value = NetworkFunctionFilter(**subscription["nfFilter"])
+        filtered_nfs = nf_service.capture_filtered_nfs(subscription["subscriptionName"])
+        subscription_service.publish_measurement_grp_to_nfs(
+            subscription["subscriptionName"], filtered_nfs, measurement_grps)
+        mock_logger.assert_called_with('Publish event failed for nf name, measure_grp_name, '
+                                       'sub_name: pnf_33_ericsson,meas2, xtraPM-All-gNB-R2B-new2 '
+                                       'with error: Publish failed')
+
+    @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')
+    @patch('mod.logger.error')
+    def test_create_subscription_all_locked_msg_grp(self, mock_logger, 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)
+        subscription = self.create_test_subs('xtraPM-All-gNB-R2B-new2', 'msrmt_grp_name-new2')
+        subscription = subscription.replace('UNLOCKED', 'LOCKED')
+        subscription = json.loads(subscription)['subscription']
+        mock_filter_call.return_value = NetworkFunctionFilter(**subscription["nfFilter"])
+        subscription_service.create_subscription(subscription)
+        mock_logger.assert_called_with('All measurement groups are locked for subscription: '
+                                       'xtraPM-All-gNB-R2B-new2, please verify/check'
+                                       ' measurement groups.')
+
+    def create_measurement_grp(self, measurement, measurement_name, admin_status):
+        new_measurement = copy.deepcopy(measurement)
+        measurement.measurement_group_name = measurement_name
+        new_measurement.administrative_state = admin_status
+        return new_measurement
+
+    @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_save_filtered_nfs(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)
+        subscription = self.create_test_subs('xtraPM-All-gNB-R2B-new', 'msrmt_grp_name-new')
+        subscription = json.loads(subscription)['subscription']
+        mock_filter_call.return_value = NetworkFunctionFilter(**subscription["nfFilter"])
+        filtered_nfs = nf_service.capture_filtered_nfs(subscription["subscriptionName"])
+        subscription_service.save_filtered_nfs(filtered_nfs)
+
+        for nf in filtered_nfs:
+            saved_nf = (NetworkFunctionModel.query.filter(
+                NetworkFunctionModel.nf_name == nf.nf_name).one_or_none())
+            self.assertIsNotNone(saved_nf)
+
+    @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_apply_subscription_to_nfs(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)
+        subscription = json.loads(self.subscription_request)['subscription']
+        mock_filter_call.return_value = NetworkFunctionFilter(**subscription["nfFilter"])
+        filtered_nfs = nf_service.capture_filtered_nfs(subscription["subscriptionName"])
+        subscription_service.apply_subscription_to_nfs(filtered_nfs, 'xtraPM-All-gNB-R2B')
+
+        for nf in filtered_nfs:
+            saved_nf_sub_rel = (NfSubRelationalModel.query.filter(
+                NfSubRelationalModel.subscription_name == 'xtraPM-All-gNB-R2B',
+                NfSubRelationalModel.nf_name == nf.nf_name).one_or_none())
+            self.assertIsNotNone(saved_nf_sub_rel)
+
+    def test_check_missing_data_sub_name_missing(self):
+        subscription = self.create_test_subs('', 'msrmt_grp_name-new')
+        subscription = json.loads(subscription)['subscription']
+        try:
+            subscription_service.check_missing_data(subscription)
+        except InvalidDataException as invalidEx:
+            self.assertEqual(invalidEx.invalid_message, "No value provided in subscription name")
+
+    def test_check_missing_data_admin_status_missing(self):
+        subscription = self.subscription_request.replace(
+            'UNLOCKED', '')
+        subscription = json.loads(subscription)['subscription']
+        try:
+            subscription_service.check_missing_data(subscription)
+        except InvalidDataException as invalidEx:
+            self.assertEqual(invalidEx.invalid_message,
+                             "No value provided for administrative state")
+
+    def test_check_missing_data_msr_grp_name(self):
+        subscription = self.create_test_subs('xtraPM-All-gNB-R2B-new', '')
+        subscription = json.loads(subscription)['subscription']
+        try:
+            subscription_service.check_missing_data(subscription)
+        except InvalidDataException as invalidEx:
+            self.assertEqual(invalidEx.invalid_message,
+                             "No value provided for measurement group name")
+
+    def test_validate_nf_filter_with_no_filter_values(self):
+        nfFilter = '{"nfNames": [],"modelInvariantIDs": [], ' \
+                   '"modelVersionIDs": [],"modelNames": []}'
+        try:
+            subscription_service.validate_nf_filter(json.loads(nfFilter))
+        except InvalidDataException as invalidEx:
+            self.assertEqual(invalidEx.invalid_message,
+                             "At least one filter within nfFilter must not be empty")
+
+    def test_db_string_to_list(self):
+        db_string = '{"*pnf","*vnf"}'
+        db_array = convert_db_string_to_list(db_string)
+        self.assertEqual(len(db_array), 2)
+
+    def test_db_string_to_list_empty(self):
+        db_string = '{}'
+        db_array = convert_db_string_to_list(db_string)
+        self.assertEqual(len(db_array), 0)
diff --git a/components/pm-subscription-handler/tests/test_aai_service.py b/components/pm-subscription-handler/tests/test_aai_service.py
index 27c0f40..97f400c 100644
--- a/components/pm-subscription-handler/tests/test_aai_service.py
+++ b/components/pm-subscription-handler/tests/test_aai_service.py
@@ -1,5 +1,5 @@
 # ============LICENSE_START===================================================
-#  Copyright (C) 2019-2020 Nordix Foundation.
+#  Copyright (C) 2019-2021 Nordix Foundation.
 # ============================================================================
 # Licensed under the Apache License, Version 2.0 (the 'License');
 # you may not use this file except in compliance with the License.
@@ -57,7 +57,7 @@
         mock_get_session.return_value.status_code = 200
         mock_get_session.return_value.text = self.good_model_info
         mock_get_sdnc_params.return_value = True
-        xnfs = aai_client.get_pmsh_nfs_from_aai(self.app_conf)
+        xnfs = aai_client.get_pmsh_nfs_from_aai(self.app_conf, self.app_conf.nf_filter)
         self.assertEqual(self.app_conf.subscription.subscriptionName, 'ExtraPM-All-gNB-R2B')
         self.assertEqual(self.app_conf.subscription.administrativeState, 'UNLOCKED')
         self.assertEqual(len(xnfs), 3)
@@ -67,7 +67,7 @@
         mock_session.return_value.status_code = 404
         with mock.patch('mod.aai_client._get_all_aai_nf_data', return_value=None):
             with self.assertRaises(RuntimeError):
-                aai_client.get_pmsh_nfs_from_aai(self.app_conf)
+                aai_client.get_pmsh_nfs_from_aai(self.app_conf, self.app_conf.nf_filter)
 
     @responses.activate
     def test_aai_client_get_all_aai_xnf_data_not_found(self):
diff --git a/components/pm-subscription-handler/tests/test_controller.py b/components/pm-subscription-handler/tests/test_controller.py
index c38cd97..a3a2816 100755
--- a/components/pm-subscription-handler/tests/test_controller.py
+++ b/components/pm-subscription-handler/tests/test_controller.py
@@ -1,5 +1,5 @@
 # ============LICENSE_START===================================================
-#  Copyright (C) 2019-2020 Nordix Foundation.
+#  Copyright (C) 2019-2021 Nordix Foundation.
 # ============================================================================
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -17,14 +17,15 @@
 # ============LICENSE_END=====================================================
 import json
 import os
-from unittest.mock import patch
-
+from unittest.mock import patch, MagicMock
 import responses
 from requests import Session
-
 from mod import aai_client
-from mod.api.controller import status, get_all_sub_to_nf_relations
+from mod.api.controller import status, get_all_sub_to_nf_relations, post_subscription
 from tests.base_setup import BaseClassSetup
+from mod.api.db_models import SubscriptionModel, NfMeasureGroupRelationalModel
+from mod.subscription import SubNfState
+from mod.network_function import NetworkFunctionFilter
 
 
 class ControllerTestCase(BaseClassSetup):
@@ -35,10 +36,14 @@
 
     def setUp(self):
         super().setUp()
+        super().setUpAppConf()
         with open(os.path.join(os.path.dirname(__file__), 'data/aai_xnfs.json'), 'r') as data:
             self.aai_response_data = data.read()
         with open(os.path.join(os.path.dirname(__file__), 'data/aai_model_info.json'), 'r') as data:
             self.good_model_info = data.read()
+        with open(os.path.join(os.path.dirname(__file__),
+                               'data/create_subscription_request.json'), 'r') as data:
+            self.subscription_request = data.read()
 
     def tearDown(self):
         super().tearDown()
@@ -62,10 +67,65 @@
                       '7129e420-d396-4efb-af02-6b83499b12f8/model-vers/model-ver/'
                       'e80a6ae3-cafd-4d24-850d-e14c084a5ca9',
                       json=json.loads(self.good_model_info), status=200)
-        self.xnfs = aai_client.get_pmsh_nfs_from_aai(self.app_conf)
+        self.xnfs = aai_client.get_pmsh_nfs_from_aai(self.app_conf, self.app_conf.nf_filter)
         sub_model = self.app_conf.subscription.get()
         for nf in self.xnfs:
             self.app_conf.subscription.add_network_function_to_subscription(nf, sub_model)
         all_subs = get_all_sub_to_nf_relations()
         self.assertEqual(len(all_subs[0]['network_functions']), 3)
         self.assertEqual(all_subs[0]['subscription_name'], 'ExtraPM-All-gNB-R2B')
+
+    def create_test_subs(self, new_sub_name, new_msrmt_grp_name):
+        subscription = self.subscription_request.replace('ExtraPM-All-gNB-R2B', new_sub_name)
+        subscription = subscription.replace('msrmt_grp_name', new_msrmt_grp_name)
+        return subscription
+
+    @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_post_subscription(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)
+        subscription = self.create_test_subs('xtraPM-All-gNB-R2B-post', 'msrmt_grp_name-post')
+        subscription = json.loads(subscription)
+        mock_filter_call.return_value = NetworkFunctionFilter(
+            **subscription['subscription']["nfFilter"])
+        sub_name = subscription['subscription']['subscriptionName']
+        mes_grp = subscription['subscription']['measurementGroups'][0]['measurementGroup']
+        mes_grp_name = mes_grp['measurementGroupName']
+        response = post_subscription(subscription)
+        subscription = (SubscriptionModel.query.filter(
+            SubscriptionModel.subscription_name == sub_name).one_or_none())
+        self.assertIsNotNone(subscription)
+        msr_grp_nf_rel = (NfMeasureGroupRelationalModel.query.filter(
+            NfMeasureGroupRelationalModel.measurement_grp_name == mes_grp_name)).all()
+        for published_event in msr_grp_nf_rel:
+            self.assertEqual(published_event.nf_measure_grp_status,
+                             SubNfState.PENDING_CREATE.value)
+        self.assertEqual(response[1], 201)
+
+    def test_post_subscription_duplicate_sub(self):
+        # Posting the same subscription request stored in previous test to get duplicate response
+        response = post_subscription(json.loads(self.subscription_request))
+        self.assertEqual(response[1], 409)
+        self.assertEqual(response[0], 'subscription Name: ExtraPM-All-gNB-R2B already exists.')
+
+    def test_post_subscription_invalid_filter(self):
+        subscription = self.create_test_subs('xtraPM-All-gNB-R2B-invalid', 'msrmt_grp_name-invalid')
+        subscription = json.loads(subscription)
+        subscription['subscription']['nfFilter']['nfNames'] = []
+        subscription['subscription']['nfFilter']['modelInvariantIDs'] = []
+        subscription['subscription']['nfFilter']['modelVersionIDs'] = []
+        subscription['subscription']['nfFilter']['modelNames'] = []
+        response = post_subscription(subscription)
+        self.assertEqual(response[1], 400)
+        self.assertEqual(response[0], 'At least one filter within nfFilter must not be empty')
+
+    def test_post_subscription_missing(self):
+        subscription = json.loads(self.subscription_request)
+        subscription['subscription']['subscriptionName'] = ''
+        response = post_subscription(subscription)
+        self.assertEqual(response[1], 400)
+        self.assertEqual(response[0], 'No value provided in subscription name')
diff --git a/components/pm-subscription-handler/tests/test_subscription.py b/components/pm-subscription-handler/tests/test_subscription.py
index 01c573e..3921aa9 100755
--- a/components/pm-subscription-handler/tests/test_subscription.py
+++ b/components/pm-subscription-handler/tests/test_subscription.py
@@ -48,7 +48,7 @@
         self.mock_mr_sub = Mock()
         self.mock_mr_pub = Mock()
         self.app_conf.subscription.create()
-        self.xnfs = aai_client.get_pmsh_nfs_from_aai(self.app_conf)
+        self.xnfs = aai_client.get_pmsh_nfs_from_aai(self.app_conf, self.app_conf.nf_filter)
         self.sub_model = self.app_conf.subscription.get()
 
     def tearDown(self):