From c6b25ffc68f66f653d44cc873a51266fb804d962 Mon Sep 17 00:00:00 2001 From: Bharath Thiruveedula Date: Mon, 10 Sep 2018 10:04:12 +0530 Subject: [PATCH] Add GET Subscriptions API to GVNFM Signed-off-by: Bharath Thiruveedula Change-Id: I1ed4989de62670fe0ecbb6af10ca2713f5fa390e Issue-ID: VFC-1048 --- lcm/lcm/nf/biz/create_subscription.py | 4 +- lcm/lcm/nf/biz/query_subscription.py | 69 ++++++++ lcm/lcm/nf/serializers/lccn_subscription.py | 4 +- lcm/lcm/nf/serializers/lccn_subscriptions.py | 21 +++ lcm/lcm/nf/tests/test_query_subscriptions.py | 201 ++++++++++++++++++++++++ lcm/lcm/nf/tests/test_subscribe_notification.py | 5 + lcm/lcm/nf/views/subscriptions_view.py | 51 +++++- 7 files changed, 350 insertions(+), 5 deletions(-) create mode 100644 lcm/lcm/nf/biz/query_subscription.py create mode 100644 lcm/lcm/nf/serializers/lccn_subscriptions.py create mode 100644 lcm/lcm/nf/tests/test_query_subscriptions.py diff --git a/lcm/lcm/nf/biz/create_subscription.py b/lcm/lcm/nf/biz/create_subscription.py index b8aa847f..f42a59c0 100644 --- a/lcm/lcm/nf/biz/create_subscription.py +++ b/lcm/lcm/nf/biz/create_subscription.py @@ -124,7 +124,9 @@ class CreateSubscription: logger.debug("SubscribeNotification--post::> Saving the subscription " "%s to the database" % self.subscription_id) links = { - "self": const.ROOT_URI + self.subscription_id + "self": { + "href": const.ROOT_URI + self.subscription_id + } } SubscriptionModel.objects.create(subscription_id=self.subscription_id, callback_uri=self.callback_uri, diff --git a/lcm/lcm/nf/biz/query_subscription.py b/lcm/lcm/nf/biz/query_subscription.py new file mode 100644 index 00000000..aedb467d --- /dev/null +++ b/lcm/lcm/nf/biz/query_subscription.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. + +import ast +import json +import logging + +from lcm.pub.database.models import SubscriptionModel +from lcm.pub.exceptions import NFLCMException + +logger = logging.getLogger(__name__) +ROOT_FILTERS = { + 'operationTypes': 'operation_types', + 'operationStates': 'operation_states', + 'notificationTypes': 'notification_types' +} +VNF_INSTANCE_FILTERS = { + "vnfInstanceId": "vnf_instance_filter" +} + + +class QuerySubscription: + def __init__(self, data, subscription_id=''): + self.subscription_id = subscription_id + self.params = data + + def query_multi_subscriptions(self): + query_data = {} + logger.debug("QueryMultiSubscriptions--get--biz::> Check for filters in query params" % self.params) + for query, value in self.params.iteritems(): + if query in ROOT_FILTERS: + query_data[ROOT_FILTERS[query] + '__icontains'] = value + for query, value in self.params.iteritems(): + if query in VNF_INSTANCE_FILTERS: + query_data[VNF_INSTANCE_FILTERS[query] + '__icontains'] = value + # Query the database with filters if the request has fields in request params, else fetch all records + if query_data: + subscriptions = SubscriptionModel.objects.filter(**query_data) + else: + subscriptions = SubscriptionModel.objects.all() + if not subscriptions.exists(): + raise NFLCMException('Subscriptions do not exist') + return [self.fill_resp_data(subscription) for subscription in subscriptions] + + def fill_resp_data(self, subscription): + subscription_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) + } + resp_data = { + 'id': subscription.subscription_id, + 'callbackUri': subscription.callback_uri, + 'filter': subscription_filter, + '_links': json.loads(subscription.links) + } + return resp_data diff --git a/lcm/lcm/nf/serializers/lccn_subscription.py b/lcm/lcm/nf/serializers/lccn_subscription.py index 32fcaa82..a4430ebe 100644 --- a/lcm/lcm/nf/serializers/lccn_subscription.py +++ b/lcm/lcm/nf/serializers/lccn_subscription.py @@ -14,13 +14,13 @@ from rest_framework import serializers +from link import LinkSerializer from lccn_filter_data import LifeCycleChangeNotificationsFilter class LinkSerializer(serializers.Serializer): - self = serializers.CharField( + self = LinkSerializer( help_text="URI of this resource.", - max_length=255, required=True, allow_null=False) diff --git a/lcm/lcm/nf/serializers/lccn_subscriptions.py b/lcm/lcm/nf/serializers/lccn_subscriptions.py new file mode 100644 index 00000000..c4f70f66 --- /dev/null +++ b/lcm/lcm/nf/serializers/lccn_subscriptions.py @@ -0,0 +1,21 @@ +# 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_subscription import LccnSubscriptionSerializer + + +class LccnSubscriptionsSerializer(serializers.ListSerializer): + child = LccnSubscriptionSerializer() diff --git a/lcm/lcm/nf/tests/test_query_subscriptions.py b/lcm/lcm/nf/tests/test_query_subscriptions.py new file mode 100644 index 00000000..f67ac272 --- /dev/null +++ b/lcm/lcm/nf/tests/test_query_subscriptions.py @@ -0,0 +1,201 @@ +# Copyright (C) 2018 Verizon. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json + +from django.test import TestCase, Client +from rest_framework import status + +from lcm.pub.database.models import SubscriptionModel + + +class TestQuerySubscriptions(TestCase): + def setUp(self): + self.client = Client() + self.subscription_id = "99442b18-a5c7-11e8-998c-bf1755941f16" + self.vnf_instance_id = "cd552c9c-ab6f-11e8-b354-236c32aa91a1" + SubscriptionModel.objects.all().delete() + self.test_single_subscription = { + "id": self.subscription_id, + "callbackUri": "http://aurl.com", + "_links": { + "self": { + "href": "/api/v1/subscriptions/99442b18-a5c7-11e8-998c-bf1755941f16" + } + }, + "filter": { + "notificationTypes": ["VnfLcmOperationOccurrenceNotification"], + "operationTypes": ["INSTANTIATE"], + "operationStates": ["STARTING"], + "vnfInstanceSubscriptionFilter": { + "vnfdIds": [], + "vnfInstanceIds": [self.vnf_instance_id], + "vnfInstanceNames": [], + "vnfProductsFromProviders": { + "vnfProvider": "vendor" + } + } + + } + } + + def tearDown(self): + pass + + def test_get_subscriptions(self): + vnf_instance_filter = { + "vnfdIds": [], + "vnfInstanceIds": [self.vnf_instance_id], + "vnfInstanceNames": [], + "vnfProductsFromProviders": { + "vnfProvider": "vendor" + } + } + links = { + "self": { + "href": "/api/v1/subscriptions/99442b18-a5c7-11e8-998c-bf1755941f16" + } + } + SubscriptionModel(subscription_id=self.subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['VnfLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + vnf_instance_filter=json.dumps(vnf_instance_filter)).save() + response = self.client.get("/api/vnflcm/v1/subscriptions", format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual([self.test_single_subscription], response.data) + + def test_get_subscriptions_with_vnf_instance_id(self): + vnf_instance_filter = { + "vnfdIds": [], + "vnfInstanceIds": [self.vnf_instance_id], + "vnfInstanceNames": [], + "vnfProductsFromProviders": { + "vnfProvider": "vendor" + } + } + links = { + "self": { + "href": "/api/v1/subscriptions/99442b18-a5c7-11e8-998c-bf1755941f16" + } + } + SubscriptionModel(subscription_id=self.subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['VnfLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + vnf_instance_filter=json.dumps(vnf_instance_filter)).save() + dummy_vnf_id = "584b35e2-b2a2-11e8-8e11-645106374fd3" + dummy_subscription_id = "947dcd2c-b2a2-11e8-b365-645106374fd4" + vnf_instance_filter["vnfInstanceIds"].append(dummy_vnf_id) + SubscriptionModel(subscription_id=dummy_subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['VnfLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + vnf_instance_filter=json.dumps(vnf_instance_filter)).save() + + response = self.client.get("/api/vnflcm/v1/subscriptions?vnfInstanceId=" + dummy_vnf_id, format='json') + expected_response = self.test_single_subscription.copy() + expected_response["id"] = dummy_subscription_id + expected_response["filter"]["vnfInstanceSubscriptionFilter"]["vnfInstanceIds"] = \ + vnf_instance_filter["vnfInstanceIds"] + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual([expected_response], response.data) + + def test_get_subscriptions_with_unknown_vnf_instance_id(self): + vnf_instance_filter = { + "vnfdIds": [], + "vnfInstanceIds": [self.vnf_instance_id], + "vnfInstanceNames": [], + "vnfProductsFromProviders": { + "vnfProvider": "vendor" + } + } + links = { + "self": { + "href": "/api/v1/subscriptions/99442b18-a5c7-11e8-998c-bf1755941f16" + } + } + SubscriptionModel(subscription_id=self.subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['VnfLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + vnf_instance_filter=json.dumps(vnf_instance_filter)).save() + response = self.client.get("/api/vnflcm/v1/subscriptions?vnfInstanceId=dummy", format='json') + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) + + def test_get_subscriptions_with_invalid_filter(self): + vnf_instance_filter = { + "vnfdIds": [], + "vnfInstanceIds": [self.vnf_instance_id], + "vnfInstanceNames": [], + "vnfProductsFromProviders": { + "vnfProvider": "vendor" + } + } + links = { + "self": { + "href": "/api/v1/subscriptions/99442b18-a5c7-11e8-998c-bf1755941f16" + } + } + SubscriptionModel(subscription_id=self.subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['VnfLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + vnf_instance_filter=json.dumps(vnf_instance_filter)).save() + response = self.client.get("/api/vnflcm/v1/subscriptions?dummy=dummy", format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_get_subscriptions_with_operation_type_filter(self): + vnf_instance_filter = { + "vnfdIds": [], + "vnfInstanceIds": [self.vnf_instance_id], + "vnfInstanceNames": [], + "vnfProductsFromProviders": { + "vnfProvider": "vendor" + } + } + links = { + "self": { + "href": "/api/v1/subscriptions/99442b18-a5c7-11e8-998c-bf1755941f16" + } + } + SubscriptionModel(subscription_id=self.subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['VnfLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + vnf_instance_filter=json.dumps(vnf_instance_filter)).save() + dummy_vnf_id = "584b35e2-b2a2-11e8-8e11-645106374fd3" + dummy_subscription_id = "947dcd2c-b2a2-11e8-b365-645106374fd4" + vnf_instance_filter["vnfInstanceIds"].append(dummy_vnf_id) + SubscriptionModel(subscription_id=dummy_subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['VnfLcmOperationOccurrenceNotification']", + operation_types="['SCALE']", + operation_states="['STARTING']", + links=json.dumps(links), + vnf_instance_filter=json.dumps(vnf_instance_filter)).save() + + response = self.client.get("/api/vnflcm/v1/subscriptions?operationTypes=SCALE", format='json') + expected_response = self.test_single_subscription.copy() + expected_response["id"] = dummy_subscription_id + expected_response["filter"]["vnfInstanceSubscriptionFilter"]["vnfInstanceIds"] = \ + vnf_instance_filter["vnfInstanceIds"] + expected_response["filter"]["operationTypes"] = ["SCALE"] + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual([expected_response], response.data) diff --git a/lcm/lcm/nf/tests/test_subscribe_notification.py b/lcm/lcm/nf/tests/test_subscribe_notification.py index 63d7bc28..8aeab63f 100644 --- a/lcm/lcm/nf/tests/test_subscribe_notification.py +++ b/lcm/lcm/nf/tests/test_subscribe_notification.py @@ -36,6 +36,7 @@ class TestSubscription(TestCase): 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(201, response.status_code) self.assertEqual(dummy_subscription["callbackUri"], response.data["callbackUri"]) self.assertEqual(temp_uuid, response.data["id"]) @@ -66,6 +67,7 @@ class TestSubscription(TestCase): 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(201, response.status_code) self.assertEqual(dummy_subscription["callbackUri"], response.data["callbackUri"]) self.assertEqual(temp_uuid, response.data["id"]) @@ -96,6 +98,7 @@ class TestSubscription(TestCase): 'error': 'Auth type should be BASIC' } response = self.client.post("/api/vnflcm/v1/subscriptions", data=dummy_subscription, format='json') + self.assertEqual(500, response.status_code) self.assertEqual(expected_data, response.data) @mock.patch("requests.get") @@ -119,6 +122,7 @@ class TestSubscription(TestCase): 'notificationTypes must be VnfLcmOperationOccurrenceNotification' } response = self.client.post("/api/vnflcm/v1/subscriptions", data=dummy_subscription, format='json') + self.assertEqual(500, response.status_code) self.assertEqual(expected_data, response.data) @mock.patch("requests.get") @@ -141,6 +145,7 @@ class TestSubscription(TestCase): 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(201, response.status_code) 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') diff --git a/lcm/lcm/nf/views/subscriptions_view.py b/lcm/lcm/nf/views/subscriptions_view.py index 876798de..4c013ee4 100644 --- a/lcm/lcm/nf/views/subscriptions_view.py +++ b/lcm/lcm/nf/views/subscriptions_view.py @@ -19,15 +19,29 @@ import traceback from drf_yasg.utils import swagger_auto_schema from lcm.nf.biz.create_subscription import CreateSubscription +from lcm.nf.biz.query_subscription import QuerySubscription 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.nf.serializers.lccn_subscriptions import LccnSubscriptionsSerializer +from lcm.nf.serializers.response import ProblemDetailsSerializer from lcm.pub.exceptions import NFLCMException logger = logging.getLogger(__name__) +VALID_FILTERS = ["operationTypes", "operationStates", "notificationTypes", "vnfInstanceId"] + + +def get_problem_details_serializer(status_code, error_message): + problem_details = { + "status": status_code, + "detail": error_message + } + problem_details_serializer = ProblemDetailsSerializer(data=problem_details) + problem_details_serializer.is_valid() + return problem_details_serializer class SubscriptionsView(APIView): @@ -35,8 +49,8 @@ class SubscriptionsView(APIView): request_body=LccnSubscriptionRequestSerializer(), responses={ status.HTTP_201_CREATED: LccnSubscriptionSerializer(), - status.HTTP_303_SEE_OTHER: "", - status.HTTP_500_INTERNAL_SERVER_ERROR: "Internal error" + status.HTTP_303_SEE_OTHER: ProblemDetailsSerializer(), + status.HTTP_500_INTERNAL_SERVER_ERROR: ProblemDetailsSerializer() } ) def post(self, request): @@ -72,3 +86,36 @@ class SubscriptionsView(APIView): logger.error(e.message) logger.error(traceback.format_exc()) return Response(data={'error': e.message}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + @swagger_auto_schema( + responses={ + status.HTTP_200_OK: LccnSubscriptionsSerializer(), + status.HTTP_400_BAD_REQUEST: ProblemDetailsSerializer(), + status.HTTP_500_INTERNAL_SERVER_ERROR: ProblemDetailsSerializer() + } + ) + def get(self, request): + logger.debug("SubscribeNotification--get::> %s" % request.query_params) + try: + if request.query_params and not set(request.query_params).issubset(set(VALID_FILTERS)): + problem_details_serializer = get_problem_details_serializer(status.HTTP_400_BAD_REQUEST, "Not a valid filter") + return Response(data=problem_details_serializer.data, status=status.HTTP_400_BAD_REQUEST) + resp_data = QuerySubscription(request.query_params).query_multi_subscriptions() + + subscriptions_serializer = LccnSubscriptionsSerializer(data=resp_data) + if not subscriptions_serializer.is_valid(): + raise NFLCMException(subscriptions_serializer.errors) + + logger.debug("SubscribeNotification--get::> Remove default fields if exclude_default" + + " is specified") + return Response(data=subscriptions_serializer.data, status=status.HTTP_200_OK) + except NFLCMException as e: + logger.error(e.message) + problem_details_serializer = get_problem_details_serializer(status.HTTP_500_INTERNAL_SERVER_ERROR, traceback.format_exc()) + return Response(data=problem_details_serializer.data, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + except Exception as e: + logger.error(e.message) + logger.error(traceback.format_exc()) + problem_details_serializer = get_problem_details_serializer(status.HTTP_500_INTERNAL_SERVER_ERROR, traceback.format_exc()) + return Response(data=problem_details_serializer.data, status=status.HTTP_500_INTERNAL_SERVER_ERROR) -- 2.16.6