summaryrefslogtreecommitdiffstats
path: root/lcm
diff options
context:
space:
mode:
authorBharath Thiruveedula <bharath.thiruveedula@verizon.com>2018-08-30 14:31:15 +0530
committerBharath Thiruveedula <bharath.thiruveedula@verizon.com>2018-08-31 15:16:27 +0530
commit8f75d6276d4f1a8c5e9da1c77f2def362db11596 (patch)
treedaa400fbaea33dc1fde44820b82a9add5509644a /lcm
parent610d88959fde44f8efefe10007c67150bc69f1db (diff)
Add subscriptions API to GVNFM
Issue-ID: VFC-999 Change-Id: I5efb5fb080f25614f805e054584f3f771d8ab302 Signed-off-by: Bharath Thiruveedula<bharath.thiruveedula@verizon.com> depends-on: Ic3cfcfaf09701b363720ccacdf9eaaef3aafd9d8
Diffstat (limited to 'lcm')
-rw-r--r--lcm/lcm/nf/biz/create_subscription.py137
-rw-r--r--lcm/lcm/nf/const.py10
-rw-r--r--lcm/lcm/nf/serializers/lccn_filter_data.py69
-rw-r--r--lcm/lcm/nf/serializers/lccn_subscription.py45
-rw-r--r--lcm/lcm/nf/serializers/lccn_subscription_request.py35
-rw-r--r--lcm/lcm/nf/serializers/subscription_auth_data.py69
-rw-r--r--lcm/lcm/nf/serializers/vnf_instance_subscription_filter.py76
-rw-r--r--lcm/lcm/nf/tests/test_subscribe_notification.py151
-rw-r--r--lcm/lcm/nf/urls.py2
-rw-r--r--lcm/lcm/nf/views/subscriptions_view.py74
-rw-r--r--lcm/lcm/pub/database/models.py17
11 files changed, 685 insertions, 0 deletions
diff --git a/lcm/lcm/nf/biz/create_subscription.py b/lcm/lcm/nf/biz/create_subscription.py
new file mode 100644
index 00000000..b8aa847f
--- /dev/null
+++ b/lcm/lcm/nf/biz/create_subscription.py
@@ -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)
diff --git a/lcm/lcm/nf/const.py b/lcm/lcm/nf/const.py
index c5ebf5d0..ecbc80fd 100644
--- a/lcm/lcm/nf/const.py
+++ b/lcm/lcm/nf/const.py
@@ -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
index 00000000..547ba094
--- /dev/null
+++ b/lcm/lcm/nf/serializers/lccn_filter_data.py
@@ -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
index 00000000..32fcaa82
--- /dev/null
+++ b/lcm/lcm/nf/serializers/lccn_subscription.py
@@ -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
index 00000000..a11de885
--- /dev/null
+++ b/lcm/lcm/nf/serializers/lccn_subscription_request.py
@@ -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
index 00000000..f6cf8a77
--- /dev/null
+++ b/lcm/lcm/nf/serializers/subscription_auth_data.py
@@ -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
index 00000000..66bf6ff6
--- /dev/null
+++ b/lcm/lcm/nf/serializers/vnf_instance_subscription_filter.py
@@ -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
index 00000000..63d7bc28
--- /dev/null
+++ b/lcm/lcm/nf/tests/test_subscribe_notification.py
@@ -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)
diff --git a/lcm/lcm/nf/urls.py b/lcm/lcm/nf/urls.py
index f279b551..b7625339 100644
--- a/lcm/lcm/nf/urls.py
+++ b/lcm/lcm/nf/urls.py
@@ -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
index 00000000..876798de
--- /dev/null
+++ b/lcm/lcm/nf/views/subscriptions_view.py
@@ -0,0 +1,74 @@
+# 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 traceback
+
+from drf_yasg.utils import swagger_auto_schema
+from lcm.nf.biz.create_subscription import CreateSubscription
+from rest_framework import status
+from rest_framework.response import Response
+from rest_framework.views import APIView
+
+from lcm.nf.serializers.lccn_subscription_request import LccnSubscriptionRequestSerializer
+from lcm.nf.serializers.lccn_subscription import LccnSubscriptionSerializer
+from lcm.pub.exceptions import NFLCMException
+
+logger = logging.getLogger(__name__)
+
+
+class SubscriptionsView(APIView):
+ @swagger_auto_schema(
+ request_body=LccnSubscriptionRequestSerializer(),
+ responses={
+ status.HTTP_201_CREATED: LccnSubscriptionSerializer(),
+ status.HTTP_303_SEE_OTHER: "",
+ status.HTTP_500_INTERNAL_SERVER_ERROR: "Internal error"
+ }
+ )
+ def post(self, request):
+ logger.debug("SubscribeNotification--post::> %s" % request.data)
+ try:
+ lccn_subscription_request_serializer = LccnSubscriptionRequestSerializer(data=request.data)
+ if not lccn_subscription_request_serializer.is_valid():
+ raise NFLCMException(lccn_subscription_request_serializer.errors)
+ subscription = CreateSubscription(
+ lccn_subscription_request_serializer.data).do_biz()
+ lccn_notifications_filter = {
+ "notificationTypes": ast.literal_eval(subscription.notification_types),
+ "operationTypes": ast.literal_eval(subscription.operation_types),
+ "operationStates": ast.literal_eval(subscription.operation_states),
+ "vnfInstanceSubscriptionFilter": json.loads(subscription.vnf_instance_filter)
+ }
+ subscription_data = {
+ "id": subscription.subscription_id,
+ "callbackUri": subscription.callback_uri,
+ "_links": json.loads(subscription.links),
+ "filter": lccn_notifications_filter
+ }
+ sub_resp_serializer = LccnSubscriptionSerializer(data=subscription_data)
+ if not sub_resp_serializer.is_valid():
+ raise NFLCMException(sub_resp_serializer.errors)
+ return Response(data=sub_resp_serializer.data, status=status.HTTP_201_CREATED)
+ except NFLCMException as e:
+ logger.error(e.message)
+ if "exists" in e.message:
+ return Response(data={'error': '%s' % e.message}, status=status.HTTP_303_SEE_OTHER)
+ return Response(data={'error': '%s' % e.message}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+ except Exception as e:
+ logger.error(e.message)
+ logger.error(traceback.format_exc())
+ return Response(data={'error': e.message}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
diff --git a/lcm/lcm/pub/database/models.py b/lcm/lcm/pub/database/models.py
index e54fae2b..4f4fc9f2 100644
--- a/lcm/lcm/pub/database/models.py
+++ b/lcm/lcm/pub/database/models.py
@@ -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)