From 9ce8ab299e1d4c20e1ce2ad6f781baed32de51ba Mon Sep 17 00:00:00 2001 From: Sirisha Gopigiri Date: Mon, 11 Mar 2019 13:58:22 +0530 Subject: Add Create NsdManagementSubscription API Add SOL 005 Create NsdManagementSubscription API Change-Id: I8154b0592454c4944ed2f17b179eed8e8b6112c4 Issue-ID: VFC-1217 Signed-off-by: Sirisha Gopigiri --- catalog/packages/biz/nsdm_subscription.py | 177 +++++++++++ catalog/packages/const.py | 19 ++ catalog/packages/serializers/nsdm_filter_data.py | 124 ++++++++ catalog/packages/serializers/nsdm_subscription.py | 75 +++++ catalog/packages/serializers/response.py | 3 + catalog/packages/tests/test_nsdm_subscription.py | 340 ++++++++++++++++++++++ catalog/packages/urls.py | 6 +- catalog/packages/views/nsdm_subscription_views.py | 105 +++++++ catalog/pub/database/models.py | 34 +++ catalog/pub/exceptions.py | 8 + 10 files changed, 888 insertions(+), 3 deletions(-) create mode 100644 catalog/packages/biz/nsdm_subscription.py create mode 100644 catalog/packages/serializers/nsdm_filter_data.py create mode 100644 catalog/packages/serializers/nsdm_subscription.py create mode 100644 catalog/packages/tests/test_nsdm_subscription.py create mode 100644 catalog/packages/views/nsdm_subscription_views.py diff --git a/catalog/packages/biz/nsdm_subscription.py b/catalog/packages/biz/nsdm_subscription.py new file mode 100644 index 00000000..19df46e0 --- /dev/null +++ b/catalog/packages/biz/nsdm_subscription.py @@ -0,0 +1,177 @@ +# Copyright (C) 2019 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 catalog.packages import const +from catalog.pub.database.models import NsdmSubscriptionModel +from catalog.pub.exceptions import CatalogException, \ + NsdmBadRequestException, NsdmDuplicateSubscriptionException +from catalog.pub.utils.values import ignore_case_get + +logger = logging.getLogger(__name__) + +PARAMSBASICKEYS = ["userName", "password"] + +PARAMSOAUTH2CLIENTCREDENTIALSKEYS = ["clientId", "clientPassword", + "tokenEndpoint"] + + +def is_filter_type_equal(new_filter, existing_filter): + return Counter(list(set(new_filter))) == Counter(existing_filter) + + +class NsdmSubscription: + + def __init__(self): + pass + + def check_callbackuri_connection(self): + logger.debug("Create Subscription --> Test Callback URI --" + "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 CatalogException("callbackUri %s returns %s status " + "code." % (self.callback_uri, + response.status_code)) + except Exception: + raise CatalogException("callbackUri %s didn't return 204 status" + "code." % self.callback_uri) + + def fill_resp_data(self, subscription): + subscription_filter = dict() + for filter_type in const.NSDM_NOTIFICATION_FILTERS: + subscription_filter[filter_type] = \ + ast.literal_eval(subscription.__dict__[filter_type]) + resp_data = { + 'id': subscription.subscriptionid, + 'callbackUri': subscription.callback_uri, + 'filter': subscription_filter, + '_links': json.loads(subscription.links) + } + return resp_data + + def create(self, data): + logger.debug("Start Create Subscription... ") + self.filter = ignore_case_get(data, "filter", {}) + self.callback_uri = ignore_case_get(data, "callbackUri") + self.authentication = ignore_case_get(data, "authentication", {}) + 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 = \ + NsdmSubscriptionModel.objects.get( + subscriptionid=self.subscription_id) + return self.fill_resp_data(subscription) + + def check_filter_types(self): + # Check if both nsdId and nsdInfoId + # or pnfdId and pnfdInfoId are present + logger.debug("Create Subscription --> Validating Filters... ") + if self.filter and \ + self.filter.get("nsdId", "") and \ + self.filter.get("nsdInfoId", ""): + raise NsdmBadRequestException("Notification Filter should contain" + " either nsdId or nsdInfoId") + if self.filter and \ + self.filter.get("pnfdId", "") and \ + self.filter.get("pnfdInfoIds", ""): + raise NsdmBadRequestException("Notification Filter should contain" + " either pnfdId or pnfdInfoIds") + + def check_valid_auth_info(self): + logger.debug("Create Subscription --> Validating Auth " + "details if provided... ") + if self.authentication.get("paramsBasic", {}) and \ + const.BASIC not in self.authentication.get("authType", ''): + raise NsdmBadRequestException('Auth type should be ' + const.BASIC) + if self.authentication.get("paramsOauth2ClientCredentials", {}) and \ + const.OAUTH2_CLIENT_CREDENTIALS not in \ + self.authentication.get("authType", ''): + raise NsdmBadRequestException('Auth type should ' + 'be ' + const.OAUTH2_CLIENT_CREDENTIALS) + if const.BASIC in self.authentication.get("authType", '') and \ + "paramsBasic" in self.authentication.keys() and \ + not is_filter_type_equal(PARAMSBASICKEYS, + self.authentication. + get("paramsBasic").keys()): + raise NsdmBadRequestException('userName and password needed ' + 'for ' + const.BASIC) + if const.OAUTH2_CLIENT_CREDENTIALS in \ + self.authentication.get("authType", '') and \ + "paramsOauth2ClientCredentials" in \ + self.authentication.keys() and \ + not is_filter_type_equal(PARAMSOAUTH2CLIENTCREDENTIALSKEYS, + self.authentication. + get("paramsOauth2ClientCredentials") + .keys()): + raise NsdmBadRequestException('clientId, clientPassword and ' + 'tokenEndpoint required ' + 'for ' + const.OAUTH2_CLIENT_CREDENTIALS) + + def check_filter_exists(self, subscription): + for filter_type in const.NSDM_NOTIFICATION_FILTERS: + if not is_filter_type_equal(self.filter.get(filter_type, []), + ast.literal_eval( + getattr(subscription, + filter_type))): + return False + return True + + def check_valid(self): + logger.debug("Create Subscription --> Checking DB if " + "same subscription exists already exists... ") + subscriptions = \ + NsdmSubscriptionModel.objects.filter( + callback_uri=self.callback_uri) + if not subscriptions.exists(): + return + for subscription in subscriptions: + if self.check_filter_exists(subscription): + raise NsdmDuplicateSubscriptionException( + "Already Subscription exists with the " + "same callbackUri and filter") + + def save_db(self): + logger.debug("Create Subscription --> Saving the subscription " + "%s to the database" % self.subscription_id) + links = { + "self": { + "href": + const.NSDM_SUBSCRIPTION_ROOT_URI + self.subscription_id + } + } + subscription_save_db = { + "subscriptionid": self.subscription_id, + "callback_uri": self.callback_uri, + "auth_info": self.authentication, + "links": json.dumps(links) + } + for filter_type in const.NSDM_NOTIFICATION_FILTERS: + subscription_save_db[filter_type] = json.dumps( + list(set(self.filter.get(filter_type, [])))) + NsdmSubscriptionModel.objects.create(**subscription_save_db) + logger.debug('Create Subscription[%s] success', self.subscription_id) diff --git a/catalog/packages/const.py b/catalog/packages/const.py index e942ffdf..522cb859 100755 --- a/catalog/packages/const.py +++ b/catalog/packages/const.py @@ -26,3 +26,22 @@ OAUTH2_CLIENT_CREDENTIALS = "OAUTH2_CLIENT_CREDENTIALS" NOTIFICATION_TYPES = ["VnfPackageOnboardingNotification", "VnfPackageChangeNotification"] VNFPKG_SUBSCRIPTION_ROOT_URI = "api/vnfpkgm/v1/subscriptions/" + +NSDM_SUBSCRIPTION_ROOT_URI = "api/nsd/v1/subscriptions/" + +NSDM_NOTIFICATION_FILTERS = ["notificationTypes", "nsdInfoId", "nsdName", + "nsdId", "nsdVersion", "nsdDesigner", + "nsdInvariantId", "vnfPkgIds", "pnfdInfoIds", + "nestedNsdInfoIds", "nsdOnboardingState", + "nsdOperationalState", "nsdUsageState", + "pnfdId", "pnfdName", "pnfdVersion", + "pnfdProvider", "pnfdInvariantId", + "pnfdOnboardingState", "pnfdUsageState"] + +NSDM_NOTIFICATION_TYPES = ["NsdOnBoardingNotification", + "NsdOnboardingFailureNotification", + "NsdChangeNotification", + "NsdDeletionNotification", + "PnfdOnBoardingNotification", + "PnfdOnBoardingFailureNotification", + "PnfdDeletionNotification"] diff --git a/catalog/packages/serializers/nsdm_filter_data.py b/catalog/packages/serializers/nsdm_filter_data.py new file mode 100644 index 00000000..d5a08910 --- /dev/null +++ b/catalog/packages/serializers/nsdm_filter_data.py @@ -0,0 +1,124 @@ +# Copyright (C) 2019 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 catalog.packages.const import NSDM_NOTIFICATION_TYPES + + +class NsdmNotificationsFilter(serializers.Serializer): + notificationTypes = serializers.ListField( + child=serializers.ChoiceField( + required=True, + choices=NSDM_NOTIFICATION_TYPES), + help_text="Match particular notification types", + allow_null=False, + required=False) + nsdInfoId = serializers.ListField( + child=serializers.UUIDField(), + help_text="Match NS packages with particular nsdInfoIds", + allow_null=False, + required=False) + nsdId = serializers.ListField( + child=serializers.UUIDField(), + help_text="Match NS Packages with particular nsdIds", + allow_null=False, + required=False) + nsdName = serializers.ListField( + child=serializers.CharField(max_length=255, required=True), + help_text="Match NS Packages with particular nsdNames", + allow_null=False, + required=False) + nsdVersion = serializers.ListField( + child=serializers.CharField(max_length=255, required=True), + help_text="match NS packages that belong to certain nsdversion", + required=False, + allow_null=False) + nsdInvariantId = serializers.ListField( + child=serializers.UUIDField(), + help_text="Match NS Packages with particular nsdInvariantIds", + allow_null=False, + required=False) + vnfPkgIds = serializers.ListField( + child=serializers.UUIDField(), + help_text="Match NS Packages that has VNF PackageIds", + allow_null=False, + required=False) + nestedNsdInfoIds = serializers.ListField( + child=serializers.UUIDField(), + help_text="Match NS Packages with particular nsdInvariantIds", + allow_null=False, + required=False) + nsdOnboardingState = serializers.ListField( + child=serializers.ChoiceField(required=True, + choices=['CREATED', 'UPLOADING', + 'PROCESSING', 'ONBOARDED']), + help_text="Match NS Packages with particular NS Onboarding State", + allow_null=False, + required=False) + nsdOperationalState = serializers.ListField( + child=serializers.ChoiceField(required=True, + choices=['ENABLED', 'DISABLED']), + help_text="Match NS Packages with particular NS Operational State", + allow_null=False, + required=False) + nsdUsageState = serializers.ListField( + child=serializers.ChoiceField(required=True, + choices=['IN_USE', 'NOT_IN_USE']), + help_text="Match NS Packages with particular NS Usage State", + allow_null=False, + required=False) + pnfdInfoIds = serializers.ListField( + child=serializers.UUIDField(), + help_text="Match PF packages with particular pnfdInfoIds", + allow_null=False, + required=False) + pnfdId = serializers.ListField( + child=serializers.UUIDField(), + help_text="Match PF packages with particular pnfdInfoIds", + allow_null=False, + required=False) + pnfdName = serializers.ListField( + child=serializers.CharField(max_length=255, required=True), + help_text="Match PF Packages with particular pnfdNames", + allow_null=False, + required=False) + pnfdVersion = serializers.ListField( + child=serializers.CharField(max_length=255, required=True), + help_text="match PF packages that belong to certain pnfd version", + required=False, + allow_null=False) + pnfdProvider = serializers.ListField( + child=serializers.CharField(max_length=255, required=True), + help_text="Match PF Packages with particular pnfdProvider", + allow_null=False, + required=False) + pnfdInvariantId = serializers.ListField( + child=serializers.UUIDField(), + help_text="Match PF Packages with particular pnfdInvariantIds", + allow_null=False, + required=False) + pnfdOnboardingState = serializers.ListField( + child=serializers.ChoiceField(required=True, + choices=['CREATED', 'UPLOADING', + 'PROCESSING', 'ONBOARDED']), + help_text="Match PF Packages with particular PNF Onboarding State ", + allow_null=False, + required=False) + pnfdUsageState = serializers.ListField( + child=serializers.ChoiceField( + required=True, choices=['IN_USE', 'NOT_IN_USE']), + help_text="Match PF Packages with particular PNF usage State", + allow_null=False, + required=False) diff --git a/catalog/packages/serializers/nsdm_subscription.py b/catalog/packages/serializers/nsdm_subscription.py new file mode 100644 index 00000000..c96c0296 --- /dev/null +++ b/catalog/packages/serializers/nsdm_subscription.py @@ -0,0 +1,75 @@ +# Copyright (C) 2019 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 link import LinkSerializer +from subscription_auth_data import SubscriptionAuthenticationSerializer +from nsdm_filter_data import NsdmNotificationsFilter + + +class NsdmSubscriptionLinkSerializer(serializers.Serializer): + self = LinkSerializer( + help_text="Links to resources related to this resource.", + required=True) + + +class NsdmSubscriptionSerializer(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 = NsdmNotificationsFilter( + help_text="Filter settings for this subscription, to define the " + "of all notifications this subscription relates to.", + required=False) + _links = NsdmSubscriptionLinkSerializer( + help_text="Links to resources related to this resource.", + required=True) + + +class NsdmSubscriptionsSerializer(serializers.ListSerializer): + child = NsdmSubscriptionSerializer() + + +class NsdmSubscriptionIdSerializer(serializers.Serializer): + subscription_id = serializers.UUIDField( + help_text="Identifier of this subscription resource.", + required=True, + allow_null=False) + + +class NsdmSubscriptionRequestSerializer(serializers.Serializer): + callbackUri = serializers.CharField( + help_text="The URI of the endpoint to send the notification to.", + required=True, + allow_null=False) + filter = NsdmNotificationsFilter( + 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/catalog/packages/serializers/response.py b/catalog/packages/serializers/response.py index 64740780..449d0622 100644 --- a/catalog/packages/serializers/response.py +++ b/catalog/packages/serializers/response.py @@ -26,3 +26,6 @@ class ProblemDetailsSerializer(serializers.Serializer): "specification or by an implementation.", required=False, allow_null=True) + + class Meta: + ref_name = 'SUBSCRIPTION_ProblemDetailsSerializer' diff --git a/catalog/packages/tests/test_nsdm_subscription.py b/catalog/packages/tests/test_nsdm_subscription.py new file mode 100644 index 00000000..fb355552 --- /dev/null +++ b/catalog/packages/tests/test_nsdm_subscription.py @@ -0,0 +1,340 @@ +# Copyright (C) 2019 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 +import uuid +from django.test import TestCase +from rest_framework.test import APIClient +from rest_framework import status + +from catalog.packages.biz.nsdm_subscription import NsdmSubscription +from catalog.pub.database.models import NsdmSubscriptionModel + + +class TestNsdmSubscription(TestCase): + + def setUp(self): + self.client = APIClient() + NsdmSubscriptionModel.objects.all().delete() + self.subscription_id = str(uuid.uuid4()) + self.subscription = { + "callbackUri": "http://callbackuri.com", + "authentication": { + "authType": ["BASIC"], + "paramsBasic": { + "userName": "username", + "password": "password" + } + } + } + self.links = { + "self": { + "href": "/api/v1/subscriptions/" + self.subscription_id + } + } + self.test_subscription = { + "callbackUri": "http://callbackuri.com", + "id": self.subscription_id, + "filter": { + "notificationTypes": [ + "NsdOnBoardingNotification" + ], + "nsdInfoId": [], + "nsdId": [], + "nsdName": [], + "nsdVersion": [], + "nsdInvariantId": [], + "vnfPkgIds": [], + "nestedNsdInfoIds": [], + "nsdOnboardingState": [], + "nsdOperationalState": [], + "nsdUsageState": [], + "pnfdInfoIds": [], + "pnfdId": [], + "pnfdName": [], + "pnfdVersion": [], + "pnfdProvider": [], + "pnfdInvariantId": [], + "pnfdOnboardingState": [], + "pnfdUsageState": [] + }, + "_links": self.links, + } + + def tearDown(self): + pass + + @mock.patch("requests.get") + @mock.patch.object(uuid, 'uuid4') + def test_nsdm_subscribe_notification(self, mock_uuid4, mock_requests): + temp_uuid = str(uuid.uuid4()) + 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/nsd/v1/subscriptions", + data=self.subscription, format='json') + self.assertEqual(201, response.status_code) + self.assertEqual(self.subscription["callbackUri"], + response.data["callbackUri"]) + self.assertEqual(temp_uuid, response.data["id"]) + + @mock.patch("requests.get") + @mock.patch.object(uuid, 'uuid4') + def test_nsdm_subscribe_callbackFailure(self, mock_uuid4, mock_requests): + temp_uuid = str(uuid.uuid4()) + mock_requests.return_value.status_code = 500 + mock_requests.get.return_value.status_code = 500 + mock_uuid4.return_value = temp_uuid + expected_data = { + 'status': 500, + 'detail': "callbackUri http://callbackuri.com didn't" + " return 204 statuscode.", + 'title': 'Creating Subscription Failed!' + } + response = self.client.post("/api/nsd/v1/subscriptions", + data=self.subscription, format='json') + self.assertEqual(500, response.status_code) + self.assertEqual(expected_data, response.data) + + @mock.patch("requests.get") + def test_nsdm_second_subscription(self, mock_requests): + mock_requests.return_value.status_code = 204 + mock_requests.get.return_value.status_code = 204 + response = self.client.post("/api/nsd/v1/subscriptions", + data=self.subscription, format='json') + self.assertEqual(201, response.status_code) + self.assertEqual(self.subscription["callbackUri"], + response.data["callbackUri"]) + dummy_subscription = { + "callbackUri": "http://callbackuri.com", + "authentication": { + "authType": ["BASIC"], + "paramsBasic": { + "userName": "username", + "password": "password" + } + }, + "filter": { + "nsdId": ["b632bddc-bccd-4180-bd8d-4e8a9578eff7"], + } + } + response = self.client.post("/api/nsd/v1/subscriptions", + data=dummy_subscription, format='json') + self.assertEqual(201, response.status_code) + self.assertEqual(dummy_subscription["callbackUri"], + response.data["callbackUri"]) + + @mock.patch("requests.get") + def test_nsdm_duplicate_subscription(self, mock_requests): + mock_requests.return_value.status_code = 204 + mock_requests.get.return_value.status_code = 204 + response = self.client.post("/api/nsd/v1/subscriptions", + data=self.subscription, format='json') + self.assertEqual(201, response.status_code) + self.assertEqual(self.subscription["callbackUri"], + response.data["callbackUri"]) + expected_data = { + 'status': 303, + 'detail': 'Already Subscription exists with' + ' the same callbackUri and filter', + 'title': 'Creating Subscription Failed!' + } + response = self.client.post("/api/nsd/v1/subscriptions", + data=self.subscription, format='json') + self.assertEqual(303, response.status_code) + self.assertEqual(expected_data, response.data) + + @mock.patch("requests.get") + def test_nsdm_bad_request(self, mock_requests): + dummy_subscription = { + "callbackUri": "http://callbackuri.com", + "authentication": { + "authType": ["BASIC"], + "paramsBasic": { + "userName": "username", + "password": "password" + } + }, + "filter": { + "nsdId": "b632bddc-bccd-4180-bd8d-4e8a9578eff7", + } + } + response = self.client.post("/api/nsd/v1/subscriptions", + data=dummy_subscription, format='json') + self.assertEqual(400, response.status_code) + + @mock.patch("requests.get") + def test_nsdm_invalid_authtype_subscription(self, mock_requests): + dummy_subscription = { + "callbackUri": "http://callbackuri.com", + "authentication": { + "authType": ["OAUTH2_CLIENT_CREDENTIALS"], + "paramsBasic": { + "userName": "username", + "password": "password" + } + } + } + mock_requests.return_value.status_code = 204 + mock_requests.get.return_value.status_code = 204 + expected_data = { + 'status': 400, + 'detail': 'Auth type should be BASIC', + 'title': 'Creating Subscription Failed!' + } + response = self.client.post("/api/nsd/v1/subscriptions", + data=dummy_subscription, format='json') + self.assertEqual(400, response.status_code) + self.assertEqual(expected_data, response.data) + + @mock.patch("requests.get") + def test_nsdm_invalid_authtype_oauthclient_subscription( + self, mock_requests): + dummy_subscription = { + "callbackUri": "http://callbackuri.com", + "authentication": { + "authType": ["BASIC"], + "paramsOauth2ClientCredentials": { + "clientId": "clientId", + "clientPassword": "password", + "tokenEndpoint": "http://tokenEndpoint" + } + } + } + mock_requests.return_value.status_code = 204 + mock_requests.get.return_value.status_code = 204 + expected_data = { + 'status': 400, + 'detail': 'Auth type should be OAUTH2_CLIENT_CREDENTIALS', + 'title': 'Creating Subscription Failed!' + } + response = self.client.post("/api/nsd/v1/subscriptions", + data=dummy_subscription, format='json') + self.assertEqual(400, response.status_code) + self.assertEqual(expected_data, response.data) + + @mock.patch("requests.get") + def test_nsdm_invalid_authparams_subscription(self, mock_requests): + dummy_subscription = { + "callbackUri": "http://callbackuri.com", + "authentication": { + "authType": ["BASIC"], + "paramsBasic": { + "userName": "username" + } + } + } + mock_requests.return_value.status_code = 204 + mock_requests.get.return_value.status_code = 204 + expected_data = { + 'status': 400, + 'detail': 'userName and password needed for BASIC', + 'title': 'Creating Subscription Failed!' + } + response = self.client.post("/api/nsd/v1/subscriptions", + data=dummy_subscription, format='json') + self.assertEqual(400, response.status_code) + self.assertEqual(expected_data, response.data) + + @mock.patch("requests.get") + def test_nsdm_invalid_authparams_oauthclient_subscription( + self, mock_requests): + dummy_subscription = { + "callbackUri": "http://callbackuri.com", + "authentication": { + "authType": ["OAUTH2_CLIENT_CREDENTIALS"], + "paramsOauth2ClientCredentials": { + "clientPassword": "password", + "tokenEndpoint": "http://tokenEndpoint" + } + } + } + mock_requests.return_value.status_code = 204 + mock_requests.get.return_value.status_code = 204 + expected_data = { + 'status': 400, + 'detail': 'clientId, clientPassword and tokenEndpoint' + ' required for OAUTH2_CLIENT_CREDENTIALS', + 'title': 'Creating Subscription Failed!' + } + response = self.client.post("/api/nsd/v1/subscriptions", + data=dummy_subscription, format='json') + self.assertEqual(400, response.status_code) + self.assertEqual(expected_data, response.data) + + @mock.patch("requests.get") + def test_nsdm_invalid_filter_subscription(self, mock_requests): + dummy_subscription = { + "callbackUri": "http://callbackuri.com", + "authentication": { + "authType": ["BASIC"], + "paramsBasic": { + "userName": "username", + "password": "password" + } + }, + "filter": { + "nsdId": ["b632bddc-bccd-4180-bd8d-4e8a9578eff7"], + "nsdInfoId": ["d0ea5ec3-0b98-438a-9bea-488230cff174"] + } + } + mock_requests.return_value.status_code = 204 + mock_requests.get.return_value.status_code = 204 + expected_data = { + 'status': 400, + 'detail': 'Notification Filter should contain' + ' either nsdId or nsdInfoId', + 'title': 'Creating Subscription Failed!' + } + response = self.client.post("/api/nsd/v1/subscriptions", + data=dummy_subscription, format='json') + self.assertEqual(400, response.status_code) + self.assertEqual(expected_data, response.data) + + @mock.patch("requests.get") + def test_nsdm_invalid_filter_pnfd_subscription(self, mock_requests): + dummy_subscription = { + "callbackUri": "http://callbackuri.com", + "authentication": { + "authType": ["BASIC"], + "paramsBasic": { + "userName": "username", + "password": "password" + } + }, + "filter": { + "pnfdId": ["b632bddc-bccd-4180-bd8d-4e8a9578eff7"], + "pnfdInfoIds": ["d0ea5ec3-0b98-438a-9bea-488230cff174"] + } + } + mock_requests.return_value.status_code = 204 + mock_requests.get.return_value.status_code = 204 + expected_data = { + 'status': 400, + 'detail': 'Notification Filter should contain' + ' either pnfdId or pnfdInfoIds', + 'title': 'Creating Subscription Failed!' + } + response = self.client.post("/api/nsd/v1/subscriptions", + data=dummy_subscription, format='json') + self.assertEqual(400, response.status_code) + self.assertEqual(expected_data, response.data) + + @mock.patch.object(NsdmSubscription, 'create') + def test_nsdmsubscription_create_when_catch_exception(self, mock_create): + mock_create.side_effect = TypeError("Unicode type") + response = self.client.post('/api/nsd/v1/subscriptions', + data=self.subscription, format='json') + self.assertEqual(response.status_code, + status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/catalog/packages/urls.py b/catalog/packages/urls.py index 1c20fc20..0add1d53 100755 --- a/catalog/packages/urls.py +++ b/catalog/packages/urls.py @@ -16,7 +16,7 @@ from django.conf.urls import url from catalog.packages.views import vnf_package_views from catalog.packages.views.vnf_package_subscription_views import SubscriptionsView -from catalog.packages.views import catalog_views, ns_descriptor_views, pnf_descriptor_views +from catalog.packages.views import catalog_views, ns_descriptor_views, pnf_descriptor_views, nsdm_subscription_views urlpatterns = [ @@ -36,8 +36,8 @@ urlpatterns = [ url(r'^api/nsd/v1/ns_descriptors$', ns_descriptor_views.ns_descriptors_rc, name='ns_descriptors_rc'), url(r'^api/nsd/v1/ns_descriptors/(?P[0-9a-zA-Z\-\_]+)$', ns_descriptor_views.ns_info_rd, name='ns_info_rd'), url(r'^api/nsd/v1/ns_descriptors/(?P[0-9a-zA-Z\-\_]+)/nsd_content$', ns_descriptor_views.nsd_content_ru, name='nsd_content_ru'), - # url(r'^api/nsd/v1/subscriptions', nsd_subscriptions.as_view(), name='subscriptions_rc'), - # url(r'^api/nsd/v1/subscriptions/(?P[0-9a-zA-Z\-\_]+)$', nsd_subscription.as_view(), name='subscription_rd'), + url(r'^api/nsd/v1/subscriptions$', nsdm_subscription_views.nsd_subscription_rc, name='nsd_subscription_rc'), + # url(r'^api/nsd/v1/subscriptions/(?P[0-9a-zA-Z\-\_]+)$', nsdm_subscription_views.nsd_subscription_rd, name='nsd_subscription_rd'), # ETSI SOL005 PNFD url(r'^api/nsd/v1/pnf_descriptors$', pnf_descriptor_views.pnf_descriptors_rc, name='pnf_descriptors_rc'), diff --git a/catalog/packages/views/nsdm_subscription_views.py b/catalog/packages/views/nsdm_subscription_views.py new file mode 100644 index 00000000..6869d782 --- /dev/null +++ b/catalog/packages/views/nsdm_subscription_views.py @@ -0,0 +1,105 @@ +# Copyright (C) 2019 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 logging +import traceback + +from drf_yasg.utils import swagger_auto_schema +from rest_framework import status +from rest_framework.decorators import api_view +from rest_framework.response import Response + +from catalog.packages.serializers.nsdm_subscription import \ + NsdmSubscriptionSerializer, \ + NsdmSubscriptionRequestSerializer +from catalog.packages.serializers.response \ + import ProblemDetailsSerializer +from catalog.pub.exceptions import \ + NsdmBadRequestException, NsdmDuplicateSubscriptionException +from catalog.packages.biz.nsdm_subscription import NsdmSubscription + + +logger = logging.getLogger(__name__) + + +def validate_data(data, serializer): + serialized_data = serializer(data=data) + if not serialized_data.is_valid(): + logger.error('Data validation failed.') + raise NsdmBadRequestException(serialized_data.errors) + return serialized_data + + +def get_problem_details_serializer(title, status_code, error_message): + problem_details = { + "title": title, + "status": status_code, + "detail": error_message + } + problem_details_serializer = ProblemDetailsSerializer(data=problem_details) + problem_details_serializer.is_valid() + return problem_details_serializer + + +@swagger_auto_schema( + method='POST', + operation_description="Create Subscription for NSD Management", + request_body=NsdmSubscriptionRequestSerializer(), + responses={ + status.HTTP_201_CREATED: NsdmSubscriptionSerializer, + status.HTTP_303_SEE_OTHER: ProblemDetailsSerializer(), + status.HTTP_400_BAD_REQUEST: ProblemDetailsSerializer(), + status.HTTP_500_INTERNAL_SERVER_ERROR: ProblemDetailsSerializer() + } +) +@api_view(http_method_names=['POST']) +def nsd_subscription_rc(request): + if request.method == 'POST': + logger.debug("SubscribeNotification--post::> %s" % request.data) + try: + title = 'Creating Subscription Failed!' + nsdm_subscription_request = \ + validate_data(request.data, + NsdmSubscriptionRequestSerializer) + subscription = NsdmSubscription().create( + nsdm_subscription_request.data) + subscription_resp = validate_data(subscription, + NsdmSubscriptionSerializer) + return Response(data=subscription_resp.data, + status=status.HTTP_201_CREATED) + except NsdmDuplicateSubscriptionException as e: + logger.error(e.message) + problem_details_serializer = \ + get_problem_details_serializer(title, + status.HTTP_303_SEE_OTHER, + e.message) + return Response(data=problem_details_serializer.data, + status=status.HTTP_303_SEE_OTHER) + except NsdmBadRequestException as e: + problem_details_serializer = \ + get_problem_details_serializer(title, + status.HTTP_400_BAD_REQUEST, + e.message) + return Response(data=problem_details_serializer.data, + status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + logger.error(e.message) + logger.error(traceback.format_exc()) + problem_details_serializer = \ + get_problem_details_serializer( + title, + status.HTTP_500_INTERNAL_SERVER_ERROR, + e.message) + return Response(data=problem_details_serializer.data, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/catalog/pub/database/models.py b/catalog/pub/database/models.py index c1e2a8ae..ef95dea9 100644 --- a/catalog/pub/database/models.py +++ b/catalog/pub/database/models.py @@ -142,6 +142,40 @@ class JobStatusModel(models.Model): return json.dumps(dict([(attr, getattr(self, attr)) for attr in [f.name for f in self._meta.fields]])) +class NsdmSubscriptionModel(models.Model): + subscriptionid = models.CharField(db_column='SUBSCRIPTIONID', max_length=255, primary_key=True) + notificationTypes = models.TextField(db_column='NOTIFICATIONTYPES', null=True) + auth_info = models.TextField(db_column='AUTHINFO', null=True) + callback_uri = models.CharField(db_column='CALLBACKURI', max_length=255) + nsdInfoId = models.TextField(db_column='NSDINFOID', null=True) + nsdId = models.TextField(db_column='NSDID', null=True) + nsdName = models.TextField(db_column='NSDNAME', null=True) + nsdVersion = models.TextField(db_column='NSDVERSION', null=True) + nsdDesigner = models.TextField(db_column='NSDDESIGNER', null=True) + nsdInvariantId = models.TextField(db_column='NSDINVARIANTID', null=True) + vnfPkgIds = models.TextField(db_column='VNFPKGIDS', null=True) + pnfdInfoIds = models.TextField(db_column='PNFDINFOIDS', null=True) + nestedNsdInfoIds = models.TextField(db_column='NESTEDNSDINFOIDS', null=True) + nsdOnboardingState = models.TextField(db_column='NSDONBOARDINGSTATE', null=True) + nsdOperationalState = models.TextField(db_column='NSDOPERATIONALSTATE', null=True) + nsdUsageState = models.TextField(db_column='NSDUSAGESTATE', null=True) + pnfdId = models.TextField(db_column='PNFDID', null=True) + pnfdName = models.TextField(db_column='PNFDNAME', null=True) + pnfdVersion = models.TextField(db_column='PNFDVERSION', null=True) + pnfdProvider = models.TextField(db_column='PNFDPROVIDER', null=True) + pnfdInvariantId = models.TextField(db_column='PNFDINVARIANTID', null=True) + pnfdOnboardingState = models.TextField(db_column='PNFDONBOARDINGSTATE', null=True) + pnfdUsageState = models.TextField(db_column='PNFDUSAGESTATE', null=True) + links = models.TextField(db_column='LINKS') + + class Meta: + db_table = 'CATALOG_NSDM_SUBSCRIPTION' + + def toJSON(self): + import json + return json.dumps(dict([(attr, getattr(self, attr)) for attr in [f.name for f in self._meta.fields]])) + + class VnfPkgSubscriptionModel(models.Model): subscription_id = models.CharField(max_length=255, primary_key=True, db_column='SUBSCRIPTION_ID') callback_uri = models.URLField(db_column="CALLBACK_URI", max_length=255) diff --git a/catalog/pub/exceptions.py b/catalog/pub/exceptions.py index 7f348d65..81379d7b 100644 --- a/catalog/pub/exceptions.py +++ b/catalog/pub/exceptions.py @@ -27,3 +27,11 @@ class VnfPkgSubscriptionException(CatalogException): class VnfPkgDuplicateSubscriptionException(CatalogException): pass + + +class NsdmBadRequestException(CatalogException): + pass + + +class NsdmDuplicateSubscriptionException(CatalogException): + pass -- cgit 1.2.3-korg