Add notification system util in GVNFM 25/64925/1
authorBharath Thiruveedula <bharath.thiruveedula@verizon.com>
Thu, 6 Sep 2018 07:18:42 +0000 (12:48 +0530)
committerBharath Thiruveedula <bharath.thiruveedula@verizon.com>
Thu, 6 Sep 2018 07:20:40 +0000 (12:50 +0530)
Change-Id: I1489722e30121aebb69b524cdaafaa44729784a2
Signed-off-by: Bharath Thiruveedula<bharath.thiruveedula@verizon.com>
Issue-ID: VFC-1096

lcm/lcm/nf/const.py
lcm/lcm/nf/serializers/lccn_filter_data.py
lcm/lcm/nf/serializers/notification_types.py [new file with mode: 0644]
lcm/lcm/nf/serializers/vnf_lcm_op_occ.py
lcm/lcm/nf/tests/test_query_vnf_lcm_op.py
lcm/lcm/pub/utils/notificationsutil.py [new file with mode: 0644]
lcm/lcm/pub/utils/tests.py

index 37205c5..8be8389 100644 (file)
@@ -35,6 +35,35 @@ OAUTH2_CLIENT_CREDENTIALS = "OAUTH2_CLIENT_CREDENTIALS"
 
 LCCNNOTIFICATION = "VnfLcmOperationOccurrenceNotification"
 
+NOTIFICATION_TYPES = [
+    "VnfLcmOperationOccurrenceNotification",
+    "VnfIdentifierCreationNotification",
+    "VnfIdentifierDeletionNotification"
+]
+
+LCM_OPERATION_TYPES = [
+    "INSTANTIATE",
+    "SCALE",
+    "SCALE_TO_LEVEL",
+    "CHANGE_FLAVOUR",
+    "TERMINATE",
+    "HEAL",
+    "OPERATE",
+    "CHANGE_EXT_CONN",
+    "MODIFY_INFO"
+]
+
+LCM_OPERATION_STATE_TYPES = [
+    "STARTING",
+    "PROCESSING",
+    "COMPLETED",
+    "FAILED_TEMP",
+    "FAILED",
+    "ROLLING_BACK",
+    "ROLLED_BACK"
+]
+
+
 inst_req_data = {
     "flavourId": "flavour_1",
     "instantiationLevelId": "instantiationLevel_1",
index 547ba09..b016a4f 100644 (file)
 from rest_framework import serializers
 
 from vnf_instance_subscription_filter import VnfInstanceSubscriptionFilter
-
-
-NOTIFICATION_TYPES = [
-    "VnfLcmOperationOccurrenceNotification",
-    "VnfIdentifierCreationNotification",
-    "VnfIdentifierDeletionNotification"]
-
-LCM_OPERATION_TYPES = [
-    "INSTANTIATE",
-    "SCALE",
-    "SCALE_TO_LEVEL",
-    "CHANGE_FLAVOUR",
-    "TERMINATE",
-    "HEAL",
-    "OPERATE",
-    "CHANGE_EXT_CONN",
-    "MODIFY_INFO"
-]
-
-LCM_OPERATION_STATE_TYPES = [
-    "STARTING",
-    "PROCESSING",
-    "COMPLETED",
-    "FAILED_TEMP",
-    "FAILED",
-    "ROLLING_BACK",
-    "ROLLED_BACK"
-]
+from lcm.nf.const import NOTIFICATION_TYPES, LCM_OPERATION_TYPES, LCM_OPERATION_STATE_TYPES
 
 
 class LifeCycleChangeNotificationsFilter(serializers.Serializer):
diff --git a/lcm/lcm/nf/serializers/notification_types.py b/lcm/lcm/nf/serializers/notification_types.py
new file mode 100644 (file)
index 0000000..5ee5eb0
--- /dev/null
@@ -0,0 +1,128 @@
+# Copyright (C) 2018 Verizon. All Rights Reserved
+#
+# 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.
+
+from rest_framework import serializers
+
+from affected_vnfcs import AffectedVnfcsSerializer
+from affected_vls import AffectedVLsSerializer
+from affected_storages import AffectedStoragesSerializer
+from lcm.nf.const import LCM_OPERATION_TYPES, LCM_OPERATION_STATE_TYPES
+from link import LinkSerializer
+from response import ProblemDetailsSerializer
+from ext_virtual_link_info import ExtVirtualLinkInfoSerializer
+from vnf_info_modifications import VnfInfoModificationsSerializer
+
+
+class LinksSerializer(serializers.Serializer):
+    vnfInstance = LinkSerializer(
+        help_text="Link to the resource representing the VNF instance to "
+        "which the notified change applies.",
+        required=True,
+        allow_null=False)
+    subscription = LinkSerializer(
+        help_text="Link to the related subscription.",
+        required=True,
+        allow_null=False)
+    vnfLcmOpOcc = LinkSerializer(
+        help_text="Link to the VNF lifecycle management operation"
+        "occurrence that this notification is related to. Shall be"
+        "present if there is a related lifecycle operation occurance.",
+        required=False,
+        allow_null=False)
+
+
+class VnfLcmOperationOccurrenceNotification(serializers.Serializer):
+    id = serializers.CharField(
+        help_text="Identifier of this notification",
+        max_length=255,
+        required=True,
+        allow_null=False)
+    notificationType = serializers.CharField(
+        help_text="Type of the notification",
+        max_length=50,
+        required=True,
+        allow_null=False)
+    subscriptionId = serializers.CharField(
+        help_text="Identifier for the subscription",
+        required=False)
+    timeStamp = serializers.CharField(
+        help_text="Date-time of the generation of the notification.",
+        required=True)
+    notificationStatus = serializers.ChoiceField(
+        help_text="Indicates whether this notification reports about the start"
+        "of a lifecycle operation or the result of a lifecycle"
+        "operation",
+        choices=["START", "RESULT"],
+        required=True)
+    operationState = serializers.ChoiceField(
+        choices=LCM_OPERATION_STATE_TYPES,
+        help_text="The state of the VNF LCM operation occurrence. ",
+        required=True)
+    vnfInstanceId = serializers.CharField(
+        help_text="The identifier of the VNF instance affected. ",
+        required=True)
+    operation = serializers.ChoiceField(
+        help_text="The lifecycle management operation.",
+        required=True,
+        choices=LCM_OPERATION_TYPES)
+    isAutomaticInvocation = serializers.BooleanField(
+        help_text="Set to true if this VNF LCM operation occurrence has"
+        "been triggered by an automated procedure inside the"
+        "VNFM. Otherwise False",
+        required=True)
+    vnfLcmOpOccId = serializers.CharField(
+        help_text="The identifier of the VNF lifecycle management"
+        "operation occurrence associated to the notification.",
+        required=True)
+    affectedVnfcs = AffectedVnfcsSerializer(
+        help_text="Information about VNFC instances that were affected " +
+        "during the lifecycle operation.",
+        required=False,
+        many=True
+    )
+    affectedVirtualLinks = AffectedVLsSerializer(
+        help_text="Information about VL instances that were affected " +
+        "during the lifecycle operation. ",
+        required=False,
+        many=True
+    )
+    affectedVirtualStorages = AffectedStoragesSerializer(
+        help_text="Information about virtualised storage instances that " +
+        "were affected during the lifecycle operation",
+        required=False,
+        many=True
+    )
+    changedInfo = VnfInfoModificationsSerializer(
+        help_text="Information about the changed VNF instance information, " +
+        "including VNF configurable properties",
+        required=False,
+        allow_null=True)
+    changedExtConnectivity = ExtVirtualLinkInfoSerializer(
+        help_text="Information about changed external connectivity, if this " +
+        "notification represents the result of a lifecycle operation occurrence. " +
+        "Shall be present if the 'notificationStatus' is set to 'RESULT' and the " +
+        "'operation' is set to 'CHANGE_EXT_CONN'. Shall be absent otherwise.",
+        many=True,
+        required=False,
+        allow_null=True)
+    error = ProblemDetailsSerializer(
+        help_text="If 'operationState' is 'FAILED_TEMP' or 'FAILED' or " +
+        "'PROCESSING' or 'ROLLING_BACK' and previous value of 'operationState' " +
+        "was 'FAILED_TEMP'  this attribute shall be present ",
+        allow_null=True,
+        required=False
+    )
+    _links = LinksSerializer(
+        help_text="Links to resources related to this resource.",
+        required=True)
index f2ef664..a2b5019 100644 (file)
@@ -18,6 +18,7 @@ from rest_framework import serializers
 from affected_vnfcs import AffectedVnfcsSerializer
 from affected_vls import AffectedVLsSerializer
 from affected_storages import AffectedStoragesSerializer
+from link import LinkSerializer
 from response import ProblemDetailsSerializer
 from ext_virtual_link_info import ExtVirtualLinkInfoSerializer
 from vnf_info_modifications import VnfInfoModificationsSerializer
@@ -68,9 +69,8 @@ class ResourceChangesSerializer(serializers.Serializer):
 
 
 class LcmOpLinkSerializer(serializers.Serializer):
-    self = serializers.CharField(
+    self = LinkSerializer(
         help_text="URI of this resource.",
-        max_length=255,
         required=True,
         allow_null=False)
     vnfInstance = serializers.CharField(
index feabe53..b08d4b0 100644 (file)
@@ -42,7 +42,9 @@ class TestVNFLcmOpOccs(TestCase):
             "changedInfo": None,
             "changedExtConnectivity": None,
             "_links": {
-                "self": "demo",
+                "self": {
+                    "href": "demo"
+                },
                 "vnfInstance": "demo"
             }
         }
@@ -58,7 +60,9 @@ class TestVNFLcmOpOccs(TestCase):
             "isCancelPending": False,
             "cancelMode": None,
             "_links": {
-                "self": "demo",
+                "self": {
+                    "href": "demo"
+                },
                 "vnfInstance": "demo"
             }
         }]
@@ -80,7 +84,9 @@ class TestVNFLcmOpOccs(TestCase):
             "changedInfo": None,
             "changedExtConnectivity": None,
             "_links": {
-                "self": "demo",
+                "self": {
+                    "href": "demo"
+                },
                 "vnfInstance": "demo"
             }
         }]
@@ -99,7 +105,7 @@ class TestVNFLcmOpOccs(TestCase):
                          grant_id=None, operation="SCALE", is_automatic_invocation=False,
                          operation_params='{}', is_cancel_pending=False, cancel_mode=None,
                          error=None, resource_changes=None, changed_ext_connectivity=None,
-                         links=json.dumps({"self": "demo", "vnfInstance": "demo"})).save()
+                         links=json.dumps({"self": {"href": "demo"}, "vnfInstance": "demo"})).save()
         response = self.client.get("/api/vnflcm/v1/vnf_lcm_op_occs", format='json')
         self.assertEqual(response.status_code, status.HTTP_200_OK)
         self.assertEqual([self.test_single_vnf_lcm_op], response.data)
@@ -122,7 +128,7 @@ class TestVNFLcmOpOccs(TestCase):
                          grant_id=None, operation="INSTANTIATE", is_automatic_invocation=False,
                          operation_params='{}', is_cancel_pending=False, cancel_mode=None,
                          error=None, resource_changes=None, changed_ext_connectivity=None,
-                         links=json.dumps({"self": "demo", "vnfInstance": "demo"})).save()
+                         links=json.dumps({"self": {"href": "demo"}, "vnfInstance": "demo"})).save()
 
         lcm_op_id = "99442b18-a5c7-11e8-998c-bf1755941f16"
         VNFLcmOpOccModel(id=lcm_op_id, operation_state="STARTING",
@@ -131,7 +137,7 @@ class TestVNFLcmOpOccs(TestCase):
                          grant_id=None, operation="SCALE", is_automatic_invocation=False,
                          operation_params='{}', is_cancel_pending=False, cancel_mode=None,
                          error=None, resource_changes=None, changed_ext_connectivity=None,
-                         links=json.dumps({"self": "demo", "vnfInstance": "demo"})).save()
+                         links=json.dumps({"self": {"href": "demo"}, "vnfInstance": "demo"})).save()
         response = self.client.get("/api/vnflcm/v1/vnf_lcm_op_occs", format='json')
         self.assertEqual(response.status_code, status.HTTP_200_OK)
         self.assertEqual(self.test_multiple_vnf_lcm_op, response.data)
@@ -153,7 +159,7 @@ class TestVNFLcmOpOccs(TestCase):
                          grant_id=None, operation="SCALE", is_automatic_invocation=False,
                          operation_params='{}', is_cancel_pending=False, cancel_mode=None,
                          error=None, resource_changes=None, changed_ext_connectivity=None,
-                         links=json.dumps({"self": "demo", "vnfInstance": "demo"})).save()
+                         links=json.dumps({"self": {"href": "demo"}, "vnfInstance": "demo"})).save()
         response = self.client.get("/api/vnflcm/v1/vnf_lcm_op_occs?exclude_default", format='json')
         self.assertEqual(response.status_code, status.HTTP_200_OK)
         self.assertEqual(self.test_vnflcmop_with_exclude_default, response.data)
@@ -167,7 +173,7 @@ class TestVNFLcmOpOccs(TestCase):
                          grant_id=None, operation="SCALE", is_automatic_invocation=False,
                          operation_params='{}', is_cancel_pending=False, cancel_mode=None,
                          error=None, resource_changes=None, changed_ext_connectivity=None,
-                         links=json.dumps({"self": "demo", "vnfInstance": "demo"})).save()
+                         links=json.dumps({"self": {"href": "demo"}, "vnfInstance": "demo"})).save()
         response = self.client.get("/api/vnflcm/v1/vnf_lcm_op_occs/" + lcm_op_id, format='json')
         self.assertEqual(response.status_code, status.HTTP_200_OK)
         self.assertEqual(self.test_single_vnf_lcm_op, response.data)
diff --git a/lcm/lcm/pub/utils/notificationsutil.py b/lcm/lcm/pub/utils/notificationsutil.py
new file mode 100644 (file)
index 0000000..3af8f22
--- /dev/null
@@ -0,0 +1,63 @@
+# Copyright (C) 2018 Verizon. All Rights Reserved
+#
+# 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.
+
+import json
+import logging
+import requests
+
+from rest_framework import status
+from requests.auth import HTTPBasicAuth
+
+from lcm.nf import const
+from lcm.pub.database.models import SubscriptionModel
+
+logger = logging.getLogger(__name__)
+
+
+class NotificationsUtil(object):
+    def __init__(self):
+        pass
+
+    def send_notification(self, notification):
+        logger.info("Send Notifications to the callbackUri")
+        filters = {
+            "vnfInstanceId": "vnf_instance_filter",
+            "operationState": "operation_states",
+            "operation": "operation_types"
+        }
+        subscriptions_filter = {v + "__contains": notification[k] for k, v in filters.iteritems()}
+
+        subscriptions = SubscriptionModel.objects.filter(**subscriptions_filter)
+        if not subscriptions.exists():
+            logger.info("No subscriptions created for the filters %s" % notification)
+            return
+        logger.info("Start sending notifications")
+        for subscription in subscriptions:
+            # set subscription id
+            notification["subscriptionId"] = subscription.subscription_id
+            callbackUri = subscription.callback_uri
+            auth_info = json.loads(subscription.auth_info)
+            if auth_info["authType"] == const.OAUTH2_CLIENT_CREDENTIALS:
+                pass
+            self.post_notification(callbackUri, auth_info, notification)
+
+    def post_notification(self, callbackUri, auth_info, notification):
+        params = auth_info.get("paramsBasic", {})
+        username = params.get("userName")
+        password = params.get("password")
+        logger.info("Sending notification to %s", callbackUri)
+        resp = requests.post(callbackUri, data=notification, auth=HTTPBasicAuth(username, password))
+        if resp.status_code != status.HTTP_204_NO_CONTENT:
+            raise Exception("Unable to send the notification to %s, due to %s" % (callbackUri, resp.text))
+        return
index 87516f0..16210db 100644 (file)
@@ -16,14 +16,16 @@ import unittest
 import mock
 import enumutil
 import fileutil
+import json
 import urllib2
 import syscomm
 import timeutil
 import values
 import platform
 
-from lcm.pub.database.models import JobStatusModel, JobModel
+from lcm.pub.database.models import JobStatusModel, JobModel, SubscriptionModel
 from lcm.pub.utils.jobutil import JobUtil
+from lcm.pub.utils.notificationsutil import NotificationsUtil
 
 
 class MockReq():
@@ -225,3 +227,78 @@ class UtilsTest(unittest.TestCase):
         self.assertEqual("def", values.ignore_case_get(data, 'abc'))
         self.assertEqual("klm", values.ignore_case_get(data, 'hig'))
         self.assertEqual("bbb", values.ignore_case_get(data, 'aaa', 'bbb'))
+
+
+class TestNotificationUtils(unittest.TestCase):
+    def setUp(self):
+        subscription_id = 1
+        auth_params = {
+            "authType": ["BASIC"],
+            "paramsBasic": {
+                "username": "username",
+                "password": "password"
+            }
+        }
+        notification_types = ["VnfLcmOperationOccurrenceNotification"]
+        operation_types = ["INSTANTIATE"]
+        operation_states = ["STARTING"]
+        vnf_instance_filter = {
+            'vnfdIds': ['99442b18-a5c7-11e8-998c-bf1755941f13', '9fe4080c-b1a3-11e8-bb96-645106374fd3'],
+            'vnfInstanceIds': ['99442b18-a5c7-11e8-998c-bf1755941f12'],
+            'vnfInstanceNames': ['demo'],
+            'vnfProductsFromProviders': {
+                'vnfProvider': u'string',
+                'vnfProducts': {
+                    'vnfProductName': 'string',
+                    'versions': {
+                        'vnfSoftwareVersion': u'string',
+                        'vnfdVersions': 'string'
+                    }
+                }
+            }
+        }
+        links = {
+            "self": "demo"
+        }
+        SubscriptionModel(subscription_id=subscription_id, callback_uri="http://demo",
+                          auth_info=json.dumps(auth_params),
+                          notification_types=json.dumps(notification_types),
+                          operation_types=json.dumps(operation_types),
+                          operation_states=json.dumps(operation_states),
+                          vnf_instance_filter=json.dumps(vnf_instance_filter),
+                          links=json.dumps(links)).save()
+
+    def tearDown(self):
+        SubscriptionModel.objects.all().delete()
+
+    @mock.patch('requests.post')
+    def test_send_notification(self, mock_post):
+        dummy_notification = {
+            "vnfInstanceId": "99442b18-a5c7-11e8-998c-bf1755941f13",
+            "operationState": "STARTING",
+            "operation": "INSTANTIATE",
+        }
+        mock_post.return_value.status_code = 204
+        NotificationsUtil().send_notification(dummy_notification)
+        mock_post.assert_called_once()
+
+    @mock.patch('requests.post')
+    def test_send_notification_with_empty_filters(self, mock_post):
+        dummy_notification = {
+            "vnfInstanceId": "9fe4080c-b1a3-11e8-bb96-645106374fd3",
+            "operationState": "",
+            "operation": "",
+        }
+        mock_post.return_value.status_code = 204
+        NotificationsUtil().send_notification(dummy_notification)
+        mock_post.assert_called_once()
+
+    @mock.patch('requests.post')
+    def test_send_notification_unmatched_filters(self, mock_post):
+        dummy_notification = {
+            "vnfInstanceId": "9fe4080c-b1a3-11e8-bb96-xxxxx",
+            "operationState": "DUMMY",
+            "operation": "DUMMY",
+        }
+        NotificationsUtil().send_notification(dummy_notification)
+        mock_post.assert_not_called()