diff options
-rw-r--r-- | lcm/lcm/nf/const.py | 29 | ||||
-rw-r--r-- | lcm/lcm/nf/serializers/lccn_filter_data.py | 29 | ||||
-rw-r--r-- | lcm/lcm/nf/serializers/notification_types.py | 128 | ||||
-rw-r--r-- | lcm/lcm/nf/serializers/vnf_lcm_op_occ.py | 4 | ||||
-rw-r--r-- | lcm/lcm/nf/tests/test_query_vnf_lcm_op.py | 22 | ||||
-rw-r--r-- | lcm/lcm/pub/utils/notificationsutil.py | 63 | ||||
-rw-r--r-- | lcm/lcm/pub/utils/tests.py | 79 |
7 files changed, 315 insertions, 39 deletions
diff --git a/lcm/lcm/nf/const.py b/lcm/lcm/nf/const.py index 37205c5a..8be83896 100644 --- a/lcm/lcm/nf/const.py +++ b/lcm/lcm/nf/const.py @@ -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", diff --git a/lcm/lcm/nf/serializers/lccn_filter_data.py b/lcm/lcm/nf/serializers/lccn_filter_data.py index 547ba094..b016a4f0 100644 --- a/lcm/lcm/nf/serializers/lccn_filter_data.py +++ b/lcm/lcm/nf/serializers/lccn_filter_data.py @@ -15,34 +15,7 @@ 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 index 00000000..5ee5eb03 --- /dev/null +++ b/lcm/lcm/nf/serializers/notification_types.py @@ -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) diff --git a/lcm/lcm/nf/serializers/vnf_lcm_op_occ.py b/lcm/lcm/nf/serializers/vnf_lcm_op_occ.py index f2ef664d..a2b50196 100644 --- a/lcm/lcm/nf/serializers/vnf_lcm_op_occ.py +++ b/lcm/lcm/nf/serializers/vnf_lcm_op_occ.py @@ -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( diff --git a/lcm/lcm/nf/tests/test_query_vnf_lcm_op.py b/lcm/lcm/nf/tests/test_query_vnf_lcm_op.py index feabe534..b08d4b05 100644 --- a/lcm/lcm/nf/tests/test_query_vnf_lcm_op.py +++ b/lcm/lcm/nf/tests/test_query_vnf_lcm_op.py @@ -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 index 00000000..3af8f22a --- /dev/null +++ b/lcm/lcm/pub/utils/notificationsutil.py @@ -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 diff --git a/lcm/lcm/pub/utils/tests.py b/lcm/lcm/pub/utils/tests.py index 87516f07..16210db6 100644 --- a/lcm/lcm/pub/utils/tests.py +++ b/lcm/lcm/pub/utils/tests.py @@ -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() |