From 8f75d6276d4f1a8c5e9da1c77f2def362db11596 Mon Sep 17 00:00:00 2001 From: Bharath Thiruveedula Date: Thu, 30 Aug 2018 14:31:15 +0530 Subject: [PATCH] Add subscriptions API to GVNFM Issue-ID: VFC-999 Change-Id: I5efb5fb080f25614f805e054584f3f771d8ab302 Signed-off-by: Bharath Thiruveedula depends-on: Ic3cfcfaf09701b363720ccacdf9eaaef3aafd9d8 --- lcm/lcm/nf/biz/create_subscription.py | 137 +++++++++++++++++++ lcm/lcm/nf/const.py | 10 ++ lcm/lcm/nf/serializers/lccn_filter_data.py | 69 ++++++++++ lcm/lcm/nf/serializers/lccn_subscription.py | 45 ++++++ .../nf/serializers/lccn_subscription_request.py | 35 +++++ lcm/lcm/nf/serializers/subscription_auth_data.py | 69 ++++++++++ .../vnf_instance_subscription_filter.py | 76 +++++++++++ lcm/lcm/nf/tests/test_subscribe_notification.py | 151 +++++++++++++++++++++ lcm/lcm/nf/urls.py | 2 + lcm/lcm/nf/views/subscriptions_view.py | 74 ++++++++++ lcm/lcm/pub/database/models.py | 17 +++ 11 files changed, 685 insertions(+) create mode 100644 lcm/lcm/nf/biz/create_subscription.py create mode 100644 lcm/lcm/nf/serializers/lccn_filter_data.py create mode 100644 lcm/lcm/nf/serializers/lccn_subscription.py create mode 100644 lcm/lcm/nf/serializers/lccn_subscription_request.py create mode 100644 lcm/lcm/nf/serializers/subscription_auth_data.py create mode 100644 lcm/lcm/nf/serializers/vnf_instance_subscription_filter.py create mode 100644 lcm/lcm/nf/tests/test_subscribe_notification.py create mode 100644 lcm/lcm/nf/views/subscriptions_view.py 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[0-9a-zA-Z_-]+)/instantiate$', InstantiateVnfView.as_view()), url(r'^api/vnflcm/v1/vnf_instances/(?P[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) -- 2.16.6