Add subscriptions API to GVNFM 59/63759/3
authorBharath Thiruveedula <bharath.thiruveedula@verizon.com>
Thu, 30 Aug 2018 09:01:15 +0000 (14:31 +0530)
committerBharath Thiruveedula <bharath.thiruveedula@verizon.com>
Fri, 31 Aug 2018 09:46:27 +0000 (15:16 +0530)
Issue-ID: VFC-999
Change-Id: I5efb5fb080f25614f805e054584f3f771d8ab302
Signed-off-by: Bharath Thiruveedula<bharath.thiruveedula@verizon.com>
depends-on: Ic3cfcfaf09701b363720ccacdf9eaaef3aafd9d8

lcm/lcm/nf/biz/create_subscription.py [new file with mode: 0644]
lcm/lcm/nf/const.py
lcm/lcm/nf/serializers/lccn_filter_data.py [new file with mode: 0644]
lcm/lcm/nf/serializers/lccn_subscription.py [new file with mode: 0644]
lcm/lcm/nf/serializers/lccn_subscription_request.py [new file with mode: 0644]
lcm/lcm/nf/serializers/subscription_auth_data.py [new file with mode: 0644]
lcm/lcm/nf/serializers/vnf_instance_subscription_filter.py [new file with mode: 0644]
lcm/lcm/nf/tests/test_subscribe_notification.py [new file with mode: 0644]
lcm/lcm/nf/urls.py
lcm/lcm/nf/views/subscriptions_view.py [new file with mode: 0644]
lcm/lcm/pub/database/models.py

diff --git a/lcm/lcm/nf/biz/create_subscription.py b/lcm/lcm/nf/biz/create_subscription.py
new file mode 100644 (file)
index 0000000..b8aa847
--- /dev/null
@@ -0,0 +1,137 @@
+# 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 ast
+import json
+import logging
+import requests
+import uuid
+
+from collections import Counter
+
+from rest_framework import status
+
+from lcm.nf import const
+from lcm.pub.database.models import SubscriptionModel
+from lcm.pub.exceptions import NFLCMException
+from lcm.pub.utils.values import ignore_case_get
+
+logger = logging.getLogger(__name__)
+
+
+def is_filter_type_equal(new_filter, existing_filter):
+    return Counter(new_filter) == Counter(existing_filter)
+
+
+class CreateSubscription:
+    def __init__(self, data):
+        self.data = data
+        self.filter = ignore_case_get(self.data, "filter", {})
+        self.callback_uri = ignore_case_get(self.data, "callbackUri")
+        self.authentication = ignore_case_get(self.data, "authentication", {})
+        self.notification_types = ignore_case_get(self.filter, "notificationTypes", [])
+        self.operation_types = ignore_case_get(self.filter, "operationTypes", [])
+        self.operation_states = ignore_case_get(self.filter, "notificationStates", [])
+        self.vnf_filter = \
+            ignore_case_get(self.filter, "vnfInstanceSubscriptionFilter", {})
+
+    def check_callbackuri_connection(self):
+        logger.debug("SubscribeNotification-post::> Sending GET request "
+                     "to %s" % self.callback_uri)
+        try:
+            response = requests.get(self.callback_uri, timeout=2)
+            if response.status_code != status.HTTP_204_NO_CONTENT:
+                raise NFLCMException("callbackUri %s returns %s status "
+                                     "code." % (self.callback_uri, response.status_code))
+        except Exception:
+            raise NFLCMException("callbackUri %s didn't return 204 status"
+                                 "code." % self.callback_uri)
+
+    def do_biz(self):
+        self.subscription_id = str(uuid.uuid4())
+        # self.check_callbackuri_connection()
+        self.check_valid_auth_info()
+        self.check_filter_types()
+        self.check_valid()
+        self.save_db()
+        subscription = SubscriptionModel.objects.get(subscription_id=self.subscription_id)
+        return subscription
+
+    def check_filter_types(self):
+        logger.debug("SubscribeNotification--post::> Validating "
+                     "operationTypes  and operationStates if exists")
+        if self.operation_types and \
+                const.LCCNNOTIFICATION not in self.notification_types:
+            raise NFLCMException("If you are setting operationTypes,"
+                                 "then notificationTypes "
+                                 "must be " + const.LCCNNOTIFICATION)
+        if self.operation_states and \
+                const.LCCNNOTIFICATION not in self.notification_types:
+            raise NFLCMException("If you are setting operationStates,"
+                                 "then notificationTypes "
+                                 "must be " + const.LCCNNOTIFICATION)
+
+    def check_valid_auth_info(self):
+        logger.debug("SubscribeNotification--post::> Validating Auth "
+                     "details if provided")
+        if self.authentication.get("paramsBasic", {}) and \
+                const.BASIC not in self.authentication.get("authType"):
+            raise NFLCMException('Auth type should be ' + const.BASIC)
+        if self.authentication.get("paramsOauth2ClientCredentials", {}) and \
+                const.OAUTH2_CLIENT_CREDENTIALS not in self.authentication.get("authType"):
+            raise NFLCMException('Auth type should be ' + const.OAUTH2_CLIENT_CREDENTIALS)
+
+    def check_filter_exists(self, sub):
+        # Check the notificationTypes, operationTypes, operationStates
+        for filter_type in ["operation_types",
+                            "notification_types", "operation_states"]:
+            if not is_filter_type_equal(getattr(self, filter_type),
+                                        ast.literal_eval(getattr(sub, filter_type))):
+                return False
+        # If all the above types are same then check vnf instance filters
+        nf_filter = json.loads(sub.vnf_instance_filter)
+        for vnf_filter_type in ["vnfdIds", "vnfInstanceIds",
+                                "vnfInstanceNames"]:
+            if not is_filter_type_equal(self.vnf_filter.get(vnf_filter_type, []),
+                                        nf_filter.get(vnf_filter_type, [])):
+                return False
+        return True
+
+    def check_valid(self):
+        logger.debug("SubscribeNotification--post::> Checking DB if "
+                     "callbackUri already exists")
+        subscriptions = SubscriptionModel.objects.filter(callback_uri=self.callback_uri)
+        if not subscriptions.exists():
+            return True
+        for subscription in subscriptions:
+            if self.check_filter_exists(subscription):
+                raise NFLCMException("Already Subscription exists with the "
+                                     "same callbackUri and filter")
+        return False
+
+    def save_db(self):
+        logger.debug("SubscribeNotification--post::> Saving the subscription "
+                     "%s to the database" % self.subscription_id)
+        links = {
+            "self": const.ROOT_URI + self.subscription_id
+        }
+        SubscriptionModel.objects.create(subscription_id=self.subscription_id,
+                                         callback_uri=self.callback_uri,
+                                         auth_info=self.authentication,
+                                         notification_types=json.dumps(self.notification_types),
+                                         operation_types=json.dumps(self.operation_types),
+                                         operation_states=json.dumps(self.operation_states),
+                                         vnf_instance_filter=json.dumps(self.vnf_filter),
+                                         links=json.dumps(links))
+        logger.debug('Create Subscription[%s] success', self.subscription_id)
index c5ebf5d..ecbc80f 100644 (file)
@@ -22,6 +22,16 @@ VNF_STATUS = enum(NULL='null', INSTANTIATING="instantiating", INACTIVE='inactive
 RESOURCE_MAP = {'Storage': 'volumn', 'Network': 'network', 'SubNetwork': 'subnet', 'Port': 'port',
                 'Flavour': 'flavor', 'Vm': 'vm'}
 
+ROOT_URI = "api/vnflcm/v1/subscriptions/"
+
+AUTH_TYPES = ["BASIC", "OAUTH2_CLIENT_CREDENTIALS", "TLS_CERT"]
+
+BASIC = "BASIC"
+
+OAUTH2_CLIENT_CREDENTIALS = "OAUTH2_CLIENT_CREDENTIALS"
+
+LCCNNOTIFICATION = "VnfLcmOperationOccurrenceNotification"
+
 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
new file mode 100644 (file)
index 0000000..547ba09
--- /dev/null
@@ -0,0 +1,69 @@
+# 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 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"
+]
+
+
+class LifeCycleChangeNotificationsFilter(serializers.Serializer):
+    notificationTypes = serializers.ListField(
+        child=serializers.ChoiceField(required=True, choices=NOTIFICATION_TYPES),
+        help_text="Match particular notification types",
+        allow_null=False,
+        required=False)
+    operationTypes = serializers.ListField(
+        child=serializers.ChoiceField(required=True, choices=LCM_OPERATION_TYPES),
+        help_text="Match particular VNF lifecycle operation types for the " +
+        "notification of type VnfLcmOperationOccurrenceNotification.",
+        allow_null=False,
+        required=False)
+    operationStates = serializers.ListField(
+        child=serializers.ChoiceField(required=True, choices=LCM_OPERATION_STATE_TYPES),
+        help_text="Match particular LCM operation state values as reported " +
+        "in notifications of type VnfLcmOperationOccurrenceNotification.",
+        allow_null=False,
+        required=False)
+    vnfInstanceSubscriptionFilter = VnfInstanceSubscriptionFilter(
+        help_text="Filter criteria to select VNF instances about which to notify.",
+        required=False,
+        allow_null=False)
diff --git a/lcm/lcm/nf/serializers/lccn_subscription.py b/lcm/lcm/nf/serializers/lccn_subscription.py
new file mode 100644 (file)
index 0000000..32fcaa8
--- /dev/null
@@ -0,0 +1,45 @@
+# 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 lccn_filter_data import LifeCycleChangeNotificationsFilter
+
+
+class LinkSerializer(serializers.Serializer):
+    self = serializers.CharField(
+        help_text="URI of this resource.",
+        max_length=255,
+        required=True,
+        allow_null=False)
+
+
+class LccnSubscriptionSerializer(serializers.Serializer):
+    id = serializers.CharField(
+        help_text="Identifier of this subscription resource.",
+        max_length=255,
+        required=True,
+        allow_null=False)
+    callbackUri = serializers.CharField(
+        help_text="The URI of the endpoint to send the notification to.",
+        max_length=255,
+        required=True,
+        allow_null=False)
+    filter = LifeCycleChangeNotificationsFilter(
+        help_text="Filter settings for this subscription, to define the " +
+        "of all notifications this subscription relates to.",
+        required=False)
+    _links = LinkSerializer(
+        help_text="Links to resources related to this resource.",
+        required=True)
diff --git a/lcm/lcm/nf/serializers/lccn_subscription_request.py b/lcm/lcm/nf/serializers/lccn_subscription_request.py
new file mode 100644 (file)
index 0000000..a11de88
--- /dev/null
@@ -0,0 +1,35 @@
+# 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 lccn_filter_data import LifeCycleChangeNotificationsFilter
+from subscription_auth_data import SubscriptionAuthenticationSerializer
+
+
+class LccnSubscriptionRequestSerializer(serializers.Serializer):
+    callbackUri = serializers.URLField(
+        help_text="The URI of the endpoint to send the notification to.",
+        required=True,
+        allow_null=False)
+    filter = LifeCycleChangeNotificationsFilter(
+        help_text="Filter settings for the subscription, to define the subset of all " +
+        "notifications this subscription relates to.",
+        required=False,
+        allow_null=True)
+    authentication = SubscriptionAuthenticationSerializer(
+        help_text="Authentication parameters to configure the use of Authorization when sending " +
+        "notifications corresponding to this subscription.",
+        required=False,
+        allow_null=True)
diff --git a/lcm/lcm/nf/serializers/subscription_auth_data.py b/lcm/lcm/nf/serializers/subscription_auth_data.py
new file mode 100644 (file)
index 0000000..f6cf8a7
--- /dev/null
@@ -0,0 +1,69 @@
+# 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 lcm.nf import const
+
+
+class OAuthCredentialsSerializer(serializers.Serializer):
+    clientId = serializers.CharField(
+        help_text="Client identifier to be used in the access token " +
+        "request of the OAuth 2.0 client credentials grant type.",
+        required=False,
+        max_length=255,
+        allow_null=False)
+    clientPassword = serializers.CharField(
+        help_text="Client password to be used in the access token " +
+        "request of the OAuth 2.0 client credentials grant type.",
+        required=False,
+        max_length=255,
+        allow_null=False)
+    tokenEndpoint = serializers.CharField(
+        help_text="The token endpoint from which the access token can " +
+        "be obtained.",
+        required=False,
+        max_length=255,
+        allow_null=False)
+
+
+class BasicAuthSerializer(serializers.Serializer):
+    userName = serializers.CharField(
+        help_text="Username to be used in HTTP Basic authentication.",
+        max_length=255,
+        required=False,
+        allow_null=False)
+    password = serializers.CharField(
+        help_text="Password to be used in HTTP Basic authentication.",
+        max_length=255,
+        required=False,
+        allow_null=False)
+
+
+class SubscriptionAuthenticationSerializer(serializers.Serializer):
+    authType = serializers.ListField(
+        child=serializers.ChoiceField(required=True, choices=const.AUTH_TYPES),
+        help_text="Defines the types of Authentication / Authorization " +
+        "which the API consumer is willing to accept when " +
+        "receiving a notification.",
+        required=True)
+    paramsBasic = BasicAuthSerializer(
+        help_text="Parameters for authentication/authorization using BASIC.",
+        required=False,
+        allow_null=False)
+    paramsOauth2ClientCredentials = OAuthCredentialsSerializer(
+        help_text="Parameters for authentication/authorization using " +
+        "OAUTH2_CLIENT_CREDENTIALS.",
+        required=False,
+        allow_null=False)
diff --git a/lcm/lcm/nf/serializers/vnf_instance_subscription_filter.py b/lcm/lcm/nf/serializers/vnf_instance_subscription_filter.py
new file mode 100644 (file)
index 0000000..66bf6ff
--- /dev/null
@@ -0,0 +1,76 @@
+# 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
+
+
+class VersionsSerializer(serializers.Serializer):
+    vnfSoftwareVersion = serializers.CharField(
+        max_length=255,
+        help_text="Software version to match.",
+        required=True)
+    vnfdVersions = serializers.ListField(
+        child=serializers.CharField(max_length=255, required=True),
+        required=False,
+        help_text="match VNF instances that belong to VNF products " +
+        "with certain VNFD versions")
+
+
+class VnfProductsSerializer(serializers.Serializer):
+    vnfProductName = serializers.CharField(
+        max_length=255,
+        help_text="Name of the VNF product to match.",
+        required=True)
+    versions = VersionsSerializer(
+        help_text="match VNF instances that belong to VNF products " +
+        "with certain versions and a certain product name, from one " +
+        "particular provider",
+        required=False,
+        allow_null=False)
+
+
+class VnfProductsProvidersSerializer(serializers.Serializer):
+    vnfProvider = serializers.CharField(
+        max_length=255,
+        help_text="Name of the VNF provider to match.",
+        required=True)
+    vnfProducts = VnfProductsSerializer(
+        help_text="match VNF instances that belong to VNF products " +
+        "with certain product names, from one particular provider",
+        required=False,
+        allow_null=False)
+
+
+class VnfInstanceSubscriptionFilter(serializers.Serializer):
+    vnfdIds = serializers.ListField(
+        child=serializers.UUIDField(),
+        help_text="VNF instances that were created based on a " +
+        "VNFD identified by one of the vnfdId values",
+        required=False,
+        allow_null=False)
+    vnfInstanceIds = serializers.ListField(
+        child=serializers.UUIDField(),
+        help_text="VNF instance IDs that has to be matched",
+        required=False,
+        allow_null=False)
+    vnfInstanceNames = serializers.ListField(
+        child=serializers.CharField(max_length=255, required=True),
+        help_text="VNF Instance names that has to be matched",
+        required=False,
+        allow_null=False)
+    vnfProductsFromProviders = VnfProductsProvidersSerializer(
+        help_text="match VNF instances that belong to VNF products " +
+        "from certain providers.",
+        required=False,
+        allow_null=False)
diff --git a/lcm/lcm/nf/tests/test_subscribe_notification.py b/lcm/lcm/nf/tests/test_subscribe_notification.py
new file mode 100644 (file)
index 0000000..63d7bc2
--- /dev/null
@@ -0,0 +1,151 @@
+# 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 mock
+from django.test import TestCase
+from rest_framework.test import APIClient
+import uuid
+
+
+class TestSubscription(TestCase):
+    def setUp(self):
+        self.client = APIClient()
+
+    def tearDown(self):
+        pass
+
+    @mock.patch("requests.get")
+    @mock.patch.object(uuid, 'uuid4')
+    def test_subscribe_notification_simple(self, mock_uuid4, mock_requests):
+        temp_uuid = "99442b18-a5c7-11e8-998c-bf1755941f13"
+        dummy_subscription = {
+            "callbackUri": "http://aurl.com"
+        }
+        mock_requests.return_value.status_code = 204
+        mock_requests.get.status_code = 204
+        mock_uuid4.return_value = temp_uuid
+        response = self.client.post("/api/vnflcm/v1/subscriptions", data=dummy_subscription, format='json')
+        self.assertEqual(dummy_subscription["callbackUri"], response.data["callbackUri"])
+        self.assertEqual(temp_uuid, response.data["id"])
+
+    @mock.patch("requests.get")
+    @mock.patch.object(uuid, 'uuid4')
+    def test_subscribe_notification(self, mock_uuid4, mock_requests):
+        temp_uuid = "99442b18-a5c7-11e8-998c-bf1755941f13"
+        dummy_subscription = {
+            "callbackUri": "http://aurl.com",
+            "authentication": {
+                "authType": ["BASIC"],
+                "paramsBasic": {
+                    "username": "username",
+                    "password": "password"
+                }
+            },
+            "filter": {
+                "notificationTypes": ["VnfLcmOperationOccurrenceNotification"],
+                "operationTypes": [
+                    "INSTANTIATE"
+                ],
+                "operationStates": [
+                    "STARTING"
+                ],
+            }
+        }
+        mock_requests.return_value.status_code = 204
+        mock_requests.get.return_value.status_code = 204
+        mock_uuid4.return_value = temp_uuid
+        response = self.client.post("/api/vnflcm/v1/subscriptions", data=dummy_subscription, format='json')
+        self.assertEqual(dummy_subscription["callbackUri"], response.data["callbackUri"])
+        self.assertEqual(temp_uuid, response.data["id"])
+
+    @mock.patch("requests.get")
+    def test_invalid_auth_subscription(self, mock_requests):
+        dummy_subscription = {
+            "callbackUri": "http://aurl.com",
+            "authentication": {
+                "authType": ["OAUTH2_CLIENT_CREDENTIALS"],
+                "paramsBasic": {
+                    "username": "username",
+                    "password": "password"
+                }
+            },
+            "filter": {
+                "notificationTypes": ["VnfLcmOperationOccurrenceNotification"],
+                "operationTypes": [
+                    "INSTANTIATE"
+                ],
+                "operationStates": [
+                    "STARTING"
+                ],
+            }
+        }
+        mock_requests.return_value.status_code = 204
+        mock_requests.get.return_value.status_code = 204
+        expected_data = {
+            'error': 'Auth type should be BASIC'
+        }
+        response = self.client.post("/api/vnflcm/v1/subscriptions", data=dummy_subscription, format='json')
+        self.assertEqual(expected_data, response.data)
+
+    @mock.patch("requests.get")
+    def test_invalid_notification_type(self, mock_requests):
+        dummy_subscription = {
+            "callbackUri": "http://aurl.com",
+            "filter": {
+                "notificationTypes": ["VnfIdentifierDeletionNotification"],
+                "operationTypes": [
+                    "INSTANTIATE"
+                ],
+                "operationStates": [
+                    "STARTING"
+                ],
+            }
+        }
+        mock_requests.return_value.status_code = 204
+        mock_requests.get.return_value.status_code = 204
+        expected_data = {
+            'error': 'If you are setting operationTypes,then ' +
+            'notificationTypes must be VnfLcmOperationOccurrenceNotification'
+        }
+        response = self.client.post("/api/vnflcm/v1/subscriptions", data=dummy_subscription, format='json')
+        self.assertEqual(expected_data, response.data)
+
+    @mock.patch("requests.get")
+    @mock.patch.object(uuid, 'uuid4')
+    def test_duplicate_subscription(self, mock_uuid4, mock_requests):
+        temp_uuid = str(uuid.uuid4())
+        dummy_subscription = {
+            "callbackUri": "http://aurl.com",
+            "filter": {
+                "notificationTypes": ["VnfLcmOperationOccurrenceNotification"],
+                "operationTypes": [
+                    "INSTANTIATE"
+                ],
+                "operationStates": [
+                    "STARTING"
+                ]
+            }
+        }
+        mock_requests.return_value.status_code = 204
+        mock_requests.get.return_value.status_code = 204
+        mock_uuid4.return_value = temp_uuid
+        response = self.client.post("/api/vnflcm/v1/subscriptions", data=dummy_subscription, format='json')
+        self.assertEqual(dummy_subscription["callbackUri"], response.data["callbackUri"])
+        self.assertEqual(temp_uuid, response.data["id"])
+        response = self.client.post("/api/vnflcm/v1/subscriptions", data=dummy_subscription, format='json')
+        self.assertEqual(303, response.status_code)
+        expected_data = {
+            "error": "Already Subscription exists with the same callbackUri and filter"
+        }
+        self.assertEqual(expected_data, response.data)
index f279b55..b762533 100644 (file)
@@ -17,8 +17,10 @@ from django.conf.urls import url
 from lcm.nf.views.curd_vnf_views import DeleteVnfAndQueryVnf, CreateVnfAndQueryVnfs
 from lcm.nf.views.instantiate_vnf_view import InstantiateVnfView
 from lcm.nf.views.terminate_vnf_view import TerminateVnfView
+from lcm.nf.views.subscriptions_view import SubscriptionsView
 
 urlpatterns = [
+    url(r'^api/vnflcm/v1/subscriptions$', SubscriptionsView.as_view()),
     url(r'^api/vnflcm/v1/vnf_instances$', CreateVnfAndQueryVnfs.as_view()),
     url(r'^api/vnflcm/v1/vnf_instances/(?P<instanceid>[0-9a-zA-Z_-]+)/instantiate$', InstantiateVnfView.as_view()),
     url(r'^api/vnflcm/v1/vnf_instances/(?P<instanceid>[0-9a-zA-Z_-]+)$', DeleteVnfAndQueryVnf.as_view()),
diff --git a/lcm/lcm/nf/views/subscriptions_view.py b/lcm/lcm/nf/views/subscriptions_view.py
new file mode 100644 (file)
index 0000000..876798d
--- /dev/null
@@ -0,0 +1,74 @@
+# Copyright (C) 2018 Verizon. All Rights Reserved\r
+#\r
+# Licensed under the Apache License, Version 2.0 (the "License");\r
+# you may not use this file except in compliance with the License.\r
+# You may obtain a copy of the License at\r
+#\r
+#         http://www.apache.org/licenses/LICENSE-2.0\r
+#\r
+# Unless required by applicable law or agreed to in writing, software\r
+# distributed under the License is distributed on an "AS IS" BASIS,\r
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+# See the License for the specific language governing permissions and\r
+# limitations under the License.\r
+\r
+import ast\r
+import json\r
+import logging\r
+import traceback\r
+\r
+from drf_yasg.utils import swagger_auto_schema\r
+from lcm.nf.biz.create_subscription import CreateSubscription\r
+from rest_framework import status\r
+from rest_framework.response import Response\r
+from rest_framework.views import APIView\r
+\r
+from lcm.nf.serializers.lccn_subscription_request import LccnSubscriptionRequestSerializer\r
+from lcm.nf.serializers.lccn_subscription import LccnSubscriptionSerializer\r
+from lcm.pub.exceptions import NFLCMException\r
+\r
+logger = logging.getLogger(__name__)\r
+\r
+\r
+class SubscriptionsView(APIView):\r
+    @swagger_auto_schema(\r
+        request_body=LccnSubscriptionRequestSerializer(),\r
+        responses={\r
+            status.HTTP_201_CREATED: LccnSubscriptionSerializer(),\r
+            status.HTTP_303_SEE_OTHER: "",\r
+            status.HTTP_500_INTERNAL_SERVER_ERROR: "Internal error"\r
+        }\r
+    )\r
+    def post(self, request):\r
+        logger.debug("SubscribeNotification--post::> %s" % request.data)\r
+        try:\r
+            lccn_subscription_request_serializer = LccnSubscriptionRequestSerializer(data=request.data)\r
+            if not lccn_subscription_request_serializer.is_valid():\r
+                raise NFLCMException(lccn_subscription_request_serializer.errors)\r
+            subscription = CreateSubscription(\r
+                lccn_subscription_request_serializer.data).do_biz()\r
+            lccn_notifications_filter = {\r
+                "notificationTypes": ast.literal_eval(subscription.notification_types),\r
+                "operationTypes": ast.literal_eval(subscription.operation_types),\r
+                "operationStates": ast.literal_eval(subscription.operation_states),\r
+                "vnfInstanceSubscriptionFilter": json.loads(subscription.vnf_instance_filter)\r
+            }\r
+            subscription_data = {\r
+                "id": subscription.subscription_id,\r
+                "callbackUri": subscription.callback_uri,\r
+                "_links": json.loads(subscription.links),\r
+                "filter": lccn_notifications_filter\r
+            }\r
+            sub_resp_serializer = LccnSubscriptionSerializer(data=subscription_data)\r
+            if not sub_resp_serializer.is_valid():\r
+                raise NFLCMException(sub_resp_serializer.errors)\r
+            return Response(data=sub_resp_serializer.data, status=status.HTTP_201_CREATED)\r
+        except NFLCMException as e:\r
+            logger.error(e.message)\r
+            if "exists" in e.message:\r
+                return Response(data={'error': '%s' % e.message}, status=status.HTTP_303_SEE_OTHER)\r
+            return Response(data={'error': '%s' % e.message}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)\r
+        except Exception as e:\r
+            logger.error(e.message)\r
+            logger.error(traceback.format_exc())\r
+            return Response(data={'error': e.message}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)\r
index e54fae2..4f4fc9f 100644 (file)
@@ -293,3 +293,20 @@ class CPInstModel(models.Model):
     relatedvl = models.CharField(db_column='RELATEDVL', max_length=255, blank=True, null=True)
     relatedcp = models.CharField(db_column='RELATEDCP', max_length=255, blank=True, null=True)
     relatedport = models.CharField(db_column='RELATEDPORT', max_length=255, blank=True, null=True)
+
+
+class SubscriptionModel(models.Model):
+    class Meta:
+        db_table = 'SUBSCRIPTION'
+    subscription_id = models.CharField(db_column='SUBSCRIPTIONID', max_length=255, primary_key=True)
+    callback_uri = models.CharField(db_column='CALLBACKURI', max_length=255)
+    auth_info = models.TextField(db_column='AUTHINFO', max_length=20000, blank=True, null=True)
+    notification_types = models.TextField(db_column='NOTIFICATIONTYPES',
+                                          null=True)
+    operation_types = models.TextField(db_column='OPERATIONTYPES',
+                                       null=True)
+    operation_states = models.TextField(db_column='OPERATIONSTATES',
+                                        null=True)
+    vnf_instance_filter = models.TextField(db_column='VNFINSTANCEFILTER',
+                                           null=True)
+    links = models.TextField(db_column='LINKS', max_length=20000)