From 8d1842182c9ef4ea15e848436df0ac5521d6fce5 Mon Sep 17 00:00:00 2001 From: yangyan Date: Fri, 1 Feb 2019 23:49:47 +0800 Subject: [PATCH] add sub api in lcm Change-Id: I56ff6f78bd2eb33fe04ab57660a8138fb9cad140 Signed-off-by: yangyan Issue-ID: VFC-1253 --- lcm/ns/biz/create_subscription.py | 159 ++++++++++++++++++ lcm/ns/biz/ns_terminate.py | 2 +- lcm/ns/biz/query_subscription.py | 75 +++++++++ lcm/ns/const.py | 130 ++++++++++++++- lcm/ns/serializers/lccn_filter_data.py | 48 ++++++ lcm/ns/serializers/lccn_subscription.py | 44 +++++ lcm/ns/serializers/lccn_subscription_request.py | 34 ++++ lcm/ns/serializers/lccn_subscriptions.py | 21 +++ lcm/ns/serializers/link.py | 21 +++ .../serializers/ns_instance_subscription_filter.py | 42 +++++ lcm/ns/serializers/response.py | 26 +++ lcm/ns/serializers/subscription_auth_data.py | 57 +++++++ lcm/ns/tests/test_query_subscriptions.py | 184 +++++++++++++++++++++ lcm/ns/tests/test_subscribe_notification.py | 156 +++++++++++++++++ lcm/ns/urls.py | 6 +- lcm/ns/views/subscriptions_view.py | 133 +++++++++++++++ lcm/pub/database/models.py | 9 +- 17 files changed, 1141 insertions(+), 6 deletions(-) create mode 100644 lcm/ns/biz/create_subscription.py create mode 100644 lcm/ns/biz/query_subscription.py create mode 100644 lcm/ns/serializers/lccn_filter_data.py create mode 100644 lcm/ns/serializers/lccn_subscription.py create mode 100644 lcm/ns/serializers/lccn_subscription_request.py create mode 100644 lcm/ns/serializers/lccn_subscriptions.py create mode 100644 lcm/ns/serializers/link.py create mode 100644 lcm/ns/serializers/ns_instance_subscription_filter.py create mode 100644 lcm/ns/serializers/response.py create mode 100644 lcm/ns/serializers/subscription_auth_data.py create mode 100644 lcm/ns/tests/test_query_subscriptions.py create mode 100644 lcm/ns/tests/test_subscribe_notification.py create mode 100644 lcm/ns/views/subscriptions_view.py diff --git a/lcm/ns/biz/create_subscription.py b/lcm/ns/biz/create_subscription.py new file mode 100644 index 00000000..e0831d79 --- /dev/null +++ b/lcm/ns/biz/create_subscription.py @@ -0,0 +1,159 @@ +# Copyright (c) 2019, CMCC Technologies Co., Ltd. + +# 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.ns import const +from lcm.pub.database.models import SubscriptionModel +from lcm.pub.exceptions import NSLCMException +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.ns_component_types = ignore_case_get( + self.filter, "nsComponentTypes", []) + self.lcm_opname_impacting_nscomponent = ignore_case_get( + self.filter, "lcmOpNameImpactingNsComponent", []) + self.lcm_opoccstatus_impacting_nscomponent = ignore_case_get( + self.filter, "lcmOpOccStatusImpactingNsComponent", []) + self.ns_filter = ignore_case_get( + self.filter, "nsInstanceSubscriptionFilter", {}) + + 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 NSLCMException("callbackUri %s returns %s status " + "code." % (self.callback_uri, response.status_code)) + except Exception: + raise NSLCMException("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 NSLCMException("If you are setting operationTypes," + "then notificationTypes " + "must be " + const.LCCNNOTIFICATION) + if self.operation_states and \ + const.LCCNNOTIFICATION not in self.notification_types: + raise NSLCMException("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 NSLCMException('Auth type should be ' + const.BASIC) + if self.authentication.get("paramsOauth2ClientCredentials", {}) and \ + const.OAUTH2_CLIENT_CREDENTIALS not in self.authentication.get("authType"): + raise NSLCMException('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", "ns_component_types", "lcm_opname_impacting_nscomponent", + "lcm_opoccstatus_impacting_nscomponent", "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 ns instance filters + ns_filter = json.loads(sub.ns_instance_filter) + for ns_filter_type in ["nsdIds", "nsInstanceIds", "vnfdIds", "pnfdIds", "nsInstanceNames"]: + if not is_filter_type_equal(self.ns_filter.get(ns_filter_type, []), + ns_filter.get(ns_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 NSLCMException("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": { + "href": 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), + ns_instance_filter=json.dumps( + self.ns_filter), + ns_component_types=json.dumps( + self.ns_component_types), + lcm_opname_impacting_nscomponent=json.dumps( + self.lcm_opname_impacting_nscomponent), + lcm_opoccstatus_impacting_nscomponent=json.dumps( + self.lcm_opoccstatus_impacting_nscomponent), + links=json.dumps(links)) + logger.debug('Create Subscription[%s] success', self.subscription_id) diff --git a/lcm/ns/biz/ns_terminate.py b/lcm/ns/biz/ns_terminate.py index 9baabf97..a8831cb2 100644 --- a/lcm/ns/biz/ns_terminate.py +++ b/lcm/ns/biz/ns_terminate.py @@ -202,7 +202,7 @@ class TerminateNsService(threading.Thread): try: ret = call_from_ns_cancel_resource('pnf', pnfinst.pnfId) if ret[0] == 0: - delete_result = "success" + delete_result = "success" except Exception as e: logger.error("[cancel_pnf_list] error[%s]!" % e.message) logger.error(traceback.format_exc()) diff --git a/lcm/ns/biz/query_subscription.py b/lcm/ns/biz/query_subscription.py new file mode 100644 index 00000000..4b36aa78 --- /dev/null +++ b/lcm/ns/biz/query_subscription.py @@ -0,0 +1,75 @@ +# Copyright (c) 2019, CMCC Technologies Co., Ltd. + +# 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 NSLCMException + +logger = logging.getLogger(__name__) +ROOT_FILTERS = { + 'operationTypes': 'operation_types', + 'operationStates': 'operation_states', + 'notificationTypes': 'notification_types', + 'nsComponentTypes': 'ns_component_types', + 'lcmOpNameImpactingNsComponent': 'lcm_opname_impacting_nscomponent', + 'lcmOpOccStatusImpactingNsComponent': 'lcm_opoccstatus_impacting_nscomponent' +} +NS_INSTANCE_FILTERS = { + "nsInstanceId": "ns_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 NS_INSTANCE_FILTERS: + query_data[NS_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 NSLCMException('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), + "nsInstanceSubscriptionFilter": json.loads(subscription.ns_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/ns/const.py b/lcm/ns/const.py index 2527efdf..bf158947 100644 --- a/lcm/ns/const.py +++ b/lcm/ns/const.py @@ -16,8 +16,134 @@ from lcm.pub.utils.enumutil import enum OWNER_TYPE = enum(VNF=0, VNFM=1, NS=2) NS_INST_STATUS = enum(EMPTY='empty', INSTANTIATING='instantiating', TERMINATING='terminating', - ACTIVE='active', FAILED='failed', INACTIVE='inactive', UPDATING='updating', SCALING='scaling', - HEALING='healing') + ACTIVE='active', FAILED='failed', INACTIVE='inactive', UPDATING='updating', + SCALING='scaling', HEALING='healing') SERVICE_TYPE = 'NetworkService' SERVICE_ROLE = 'NetworkService' + +HEAL_ACTION_TYPE = enum(START="vmCreate", RESTART="vmReset") +ACTION_TYPE = enum(START=1, STOP=2, REBOOT=3) +GRANT_TYPE = enum(INSTANTIATE="INSTANTIATE", TERMINATE="TERMINATE", HEAL_CREATE="Heal Create", + HEAL_RESTART="Heal Restart", OPERATE="OPERATE") +VNF_STATUS = enum(NULL='null', INSTANTIATING="instantiating", INACTIVE='inactive', ACTIVE="active", + FAILED="failed", TERMINATING="terminating", SCALING="scaling", OPERATING="operating", + UPDATING="updating", HEALING="healing") + +OPERATION_TYPE = enum( + INSTANTIATE="INSTANTIATE", + SCALE="SCALE", + TERMINATE="TERMINATE", + UPDATE="UPDATE", + HEAL="HEAL", +) + + +LCM_NOTIFICATION_STATUS = enum(START="START", RESULT="RESULT") + +OPERATION_STATE_TYPE = enum( + STARTING="STARTING", + PROCESSING="PROCESSING", + COMPLETED="COMPLETED", + FAILED_TEMP="FAILED_TEMP", + FAILED="FAILED", + ROLLING_BACK="ROLLING_BACK", + ROLLED_BACK="ROLLED_BACK" +) + +COMPOMENT_TYPE = enum( + VNF="VNF", + PNF="PNF", + NS="NS", +) + +OPName_For_Change_Notification_Type = enum( + VNF_INSTANTIATE="VNF_INSTANTIATE", VNF_SCALE="VNF_SCALE", VNF_SCALE_TO_LEVEL="VNF_SCALE_TO_LEVEL", + VNF_CHANGE_FLAVOUR="VNF_CHANGE_FLAVOUR", VNF_TERMINATE="VNF_TERMINATE", VNF_HEAL="VNF_HEAL", + VNF_OPERATE="VNF_OPERATE", VNF_CHANGE_EXT_CONN="VNF_CHANGE_EXT_CONN", VNF_MODIFY_INFO="VNF_MODIFY_INFO", + NS_INSTANTIATE="NS_INSTANTIATE", NS_SCALE="NS_SCALE", NS_UPDATE="NS_UPDATE", NS_TERMINATE="NS_TERMINATE", + NS_HEAL="NS_HEAL", +) + +OpOcc_Status_For_ChangeNotification_Type = enum( + START="START", COMPLETED="COMPLETED ", PARTIALLY_COMPLETED="PARTIALLY_COMPLETED", FAILED="FAILED", + ROLLED_BACK="ROLLED_BACK", +) + +AUTH_TYPES = ["BASIC", "OAUTH2_CLIENT_CREDENTIALS", "TLS_CERT"] + +BASIC = "BASIC" + +OAUTH2_CLIENT_CREDENTIALS = "OAUTH2_CLIENT_CREDENTIALS" + +# CHANGE_TYPE = enum( +# ADDED='ADDED', +# REMOVED='REMOVED', +# MODIFIED='MODIFIED', +# TEMPORARY='TEMPORARY', +# LINK_PORT_ADDED='LINK_PORT_ADDED', +# LINK_PORT_REMOVED='LINK_PORT_REMOVED' +# ) + +# RESOURCE_MAP = {'Storage': 'volumn', 'Network': 'network', 'SubNetwork': 'subnet', 'Port': 'port', +# 'Flavour': 'flavor', 'Vm': 'vm'} + +ROOT_URI = "api/nslcm/v1/subscriptions/" + +LCCNNOTIFICATION = "NsLcmOperationOccurrenceNotification" + +NOTIFICATION_TYPES = [ + "NsLcmOperationOccurrenceNotification", "NsIdentifierCreationNotification", + "NsIdentifierDeletionNotification", + "NsChangeNotification", +] + +NS_LCM_OP_TYPES = [ + OPERATION_TYPE.INSTANTIATE, + OPERATION_TYPE.SCALE, + OPERATION_TYPE.TERMINATE, + OPERATION_TYPE.HEAL, + OPERATION_TYPE.UPDATE, +] + +LCM_OPERATION_STATE_TYPES = [ + OPERATION_STATE_TYPE.STARTING, + OPERATION_STATE_TYPE.PROCESSING, + OPERATION_STATE_TYPE.COMPLETED, + OPERATION_STATE_TYPE.FAILED_TEMP, + OPERATION_STATE_TYPE.FAILED, + OPERATION_STATE_TYPE.ROLLING_BACK, + OPERATION_STATE_TYPE.ROLLED_BACK +] + +NS_COMPOMENT_TYPE = [ + COMPOMENT_TYPE.VNF, + COMPOMENT_TYPE.PNF, + COMPOMENT_TYPE.NS, +] + + +LCM_OPName_For_Change_Notification_Type = [ + OPName_For_Change_Notification_Type.VNF_INSTANTIATE, + OPName_For_Change_Notification_Type.VNF_SCALE, + OPName_For_Change_Notification_Type.VNF_SCALE_TO_LEVEL, + OPName_For_Change_Notification_Type.VNF_CHANGE_FLAVOUR, + OPName_For_Change_Notification_Type.VNF_TERMINATE, + OPName_For_Change_Notification_Type.VNF_HEAL, + OPName_For_Change_Notification_Type.VNF_OPERATE, + OPName_For_Change_Notification_Type.VNF_CHANGE_EXT_CONN, + OPName_For_Change_Notification_Type.VNF_MODIFY_INFO, + OPName_For_Change_Notification_Type.NS_INSTANTIATE, + OPName_For_Change_Notification_Type.NS_SCALE, + OPName_For_Change_Notification_Type.NS_UPDATE, + OPName_For_Change_Notification_Type.NS_TERMINATE, + OPName_For_Change_Notification_Type.NS_HEAL, +] + +LCM_OpOcc_Status_For_ChangeNotification_Type = [ + OpOcc_Status_For_ChangeNotification_Type.START, + OpOcc_Status_For_ChangeNotification_Type.COMPLETED, + OpOcc_Status_For_ChangeNotification_Type.PARTIALLY_COMPLETED, + OpOcc_Status_For_ChangeNotification_Type.FAILED, + OpOcc_Status_For_ChangeNotification_Type.ROLLED_BACK, +] diff --git a/lcm/ns/serializers/lccn_filter_data.py b/lcm/ns/serializers/lccn_filter_data.py new file mode 100644 index 00000000..fe4c8304 --- /dev/null +++ b/lcm/ns/serializers/lccn_filter_data.py @@ -0,0 +1,48 @@ +# Copyright (c) 2019, CMCC Technologies Co., Ltd. + +# 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 ns_instance_subscription_filter import NsInstanceSubscriptionFilter +from lcm.ns.const import NOTIFICATION_TYPES, NS_LCM_OP_TYPES, LCM_OPERATION_STATE_TYPES, NS_COMPOMENT_TYPE,\ + LCM_OPName_For_Change_Notification_Type, LCM_OpOcc_Status_For_ChangeNotification_Type + + +class LifeCycleChangeNotificationsFilter(serializers.Serializer): + nsInstanceSubscriptionFilter = NsInstanceSubscriptionFilter( + help_text="Filter criteria to select NS instances about which to notify.", + required=False, allow_null=False) + 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=NS_LCM_OP_TYPES), + help_text="Match particular NS lifecycle operation types for the notification of type " + "NsLcmOperationOccurrenceNotification.", 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 " + "NsLcmOperationOccurrenceNotification.", allow_null=False, required=False) + nsComponentTypes = serializers.ListField( + child=serializers.ChoiceField(required=True, choices=NS_COMPOMENT_TYPE), + help_text="Match particular NS component types for the notification of type NsChangeNotification. ", + required=False, allow_null=False) + lcmOpNameImpactingNsComponent = serializers.ListField( + child=serializers.ChoiceField(required=True, choices=LCM_OPName_For_Change_Notification_Type), + help_text="Match particular LCM operation names for the notification of type NsChangeNotification. ", + required=False, allow_null=False) + lcmOpOccStatusImpactingNsComponent = serializers.ListField( + child=serializers.ChoiceField(required=True, choices=LCM_OpOcc_Status_For_ChangeNotification_Type), + help_text="Match particular LCM operation status values as reported in notifications of type " + "NsChangeNotification.", required=False, allow_null=False) diff --git a/lcm/ns/serializers/lccn_subscription.py b/lcm/ns/serializers/lccn_subscription.py new file mode 100644 index 00000000..032bf0e5 --- /dev/null +++ b/lcm/ns/serializers/lccn_subscription.py @@ -0,0 +1,44 @@ +# Copyright (c) 2019, CMCC Technologies Co., Ltd. + +# 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 lccn_filter_data import LifeCycleChangeNotificationsFilter + + +class LinkSerializer(serializers.Serializer): + self = LinkSerializer( + help_text="URI of this resource.", + 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 A particular notification is sent to the subscriber if the filter" + " matches, or if there is no filter.", required=False) + _links = LinkSerializer( + help_text="Links to resources related to this resource.", required=True) diff --git a/lcm/ns/serializers/lccn_subscription_request.py b/lcm/ns/serializers/lccn_subscription_request.py new file mode 100644 index 00000000..9a5437ef --- /dev/null +++ b/lcm/ns/serializers/lccn_subscription_request.py @@ -0,0 +1,34 @@ +# Copyright (c) 2019, CMCC Technologies Co., Ltd. + +# 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.CharField( + help_text="The URI of the endpoint to send the notification to.", + required=True, + allow_null=False) + filter = LifeCycleChangeNotificationsFilter( + help_text="Filter settings for this subscription, to define the subset of all notifications this" + " subscription relates to A particular notification is sent to the subscriber if the " + "filter matches, or if there is no filter.", required=False, allow_null=True) + authentication = SubscriptionAuthenticationSerializer( + help_text="Authentication parameters to conFigure the use of Authorization when sending " + "notifications corresponding to this subscription, as defined in clause 4.5.3 This" + " attribute shall only be present if the subscriber requires authorization of" + " notifications.", required=False, allow_null=True) diff --git a/lcm/ns/serializers/lccn_subscriptions.py b/lcm/ns/serializers/lccn_subscriptions.py new file mode 100644 index 00000000..82a80384 --- /dev/null +++ b/lcm/ns/serializers/lccn_subscriptions.py @@ -0,0 +1,21 @@ +# Copyright (c) 2019, CMCC Technologies Co., Ltd. + +# 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/ns/serializers/link.py b/lcm/ns/serializers/link.py new file mode 100644 index 00000000..ef5232e3 --- /dev/null +++ b/lcm/ns/serializers/link.py @@ -0,0 +1,21 @@ +# Copyright (c) 2019, CMCC Technologies Co., Ltd. + +# 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 LinkSerializer(serializers.Serializer): + href = serializers.CharField( + help_text="URI of the referenced resource.", required=True, allow_null=False, allow_blank=False) diff --git a/lcm/ns/serializers/ns_instance_subscription_filter.py b/lcm/ns/serializers/ns_instance_subscription_filter.py new file mode 100644 index 00000000..eb1d040e --- /dev/null +++ b/lcm/ns/serializers/ns_instance_subscription_filter.py @@ -0,0 +1,42 @@ +# Copyright (c) 2019, CMCC Technologies Co., Ltd. + +# 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 NsInstanceSubscriptionFilter(serializers.Serializer): + nsdIds = serializers.ListField( + child=serializers.UUIDField(), + help_text="If present, match NS instances that were created based on a NSD identified by one of the" + " nsdId values listed in this attribute.", required=False, allow_null=False) + vnfdIds = serializers.ListField( + child=serializers.UUIDField(), + help_text="If present, match NS instances that contain VNF instances that were created based on" + " identified by one of the vnfdId values listed in this attribute.", + required=False, allow_null=False) + pnfdIds = serializers.ListField( + child=serializers.UUIDField(), + help_text="If present, match NS instances that contain PNFs that are represented by a PNFD" + " identified by one of the pnfdId values listed in this attribute", + required=False, allow_null=False) + nsInstanceIds = serializers.ListField( + child=serializers.UUIDField(), + help_text="If present, match NS instances with an instance identifier listed in this attribute", + required=False, + allow_null=False) + nsInstanceNames = serializers.ListField( + child=serializers.CharField(max_length=255, required=True), + help_text="If present, match NS instances with a NS Instance Name listed in this attribute.", + required=False, + allow_null=False) diff --git a/lcm/ns/serializers/response.py b/lcm/ns/serializers/response.py new file mode 100644 index 00000000..95551ef5 --- /dev/null +++ b/lcm/ns/serializers/response.py @@ -0,0 +1,26 @@ +# Copyright (c) 2019, CMCC Technologies Co., Ltd. + +# 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 ProblemDetailsSerializer(serializers.Serializer): + type = serializers.CharField(help_text="Type", required=False, allow_null=True) + title = serializers.CharField(help_text="Title", required=False, allow_null=True) + status = serializers.IntegerField(help_text="Status", required=True) + detail = serializers.CharField(help_text="Detail", required=True, allow_null=True) + instance = serializers.CharField(help_text="Instance", required=False, allow_null=True) + additional_details = serializers.ListField( + help_text="Any number of additional attributes, as defined in a specification or by an" + " implementation.", required=False, allow_null=True) diff --git a/lcm/ns/serializers/subscription_auth_data.py b/lcm/ns/serializers/subscription_auth_data.py new file mode 100644 index 00000000..d5680b05 --- /dev/null +++ b/lcm/ns/serializers/subscription_auth_data.py @@ -0,0 +1,57 @@ +# Copyright (c) 2019, CMCC Technologies Co., Ltd. + +# 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.ns 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/ns/tests/test_query_subscriptions.py b/lcm/ns/tests/test_query_subscriptions.py new file mode 100644 index 00000000..87e98b8f --- /dev/null +++ b/lcm/ns/tests/test_query_subscriptions.py @@ -0,0 +1,184 @@ +# Copyright (c) 2019, CMCC Technologies Co., Ltd. + +# 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.ns_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": ["NsLcmOperationOccurrenceNotification"], + "operationTypes": ["INSTANTIATE"], + "operationStates": ["STARTING"], + # "nsComponentTypes": ["NS"], + "nsInstanceSubscriptionFilter": { + "nsdIds": [], + "nsInstanceIds": [self.ns_instance_id], + "nsInstanceNames": [] + } + } + } + + def tearDown(self): + pass + + def test_get_subscriptions(self): + ns_instance_filter = { + "nsdIds": [], + "nsInstanceIds": [self.ns_instance_id], + "nsInstanceNames": [] + } + 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="['NsLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + # ns_component_types="['NS']", + links=json.dumps(links), + ns_instance_filter=json.dumps(ns_instance_filter)).save() + response = self.client.get("/api/nslcm/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_ns_instance_id(self): + ns_instance_filter = { + "nsdIds": [], + "nsInstanceIds": [self.ns_instance_id], + "nsInstanceNames": [] + } + 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="['NsLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + ns_instance_filter=json.dumps(ns_instance_filter)).save() + dummy_ns_id = "584b35e2-b2a2-11e8-8e11-645106374fd3" + dummy_subscription_id = "947dcd2c-b2a2-11e8-b365-645106374fd4" + ns_instance_filter["nsInstanceIds"].append(dummy_ns_id) + SubscriptionModel(subscription_id=dummy_subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['NsLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + ns_instance_filter=json.dumps(ns_instance_filter)).save() + + response = self.client.get("/api/nslcm/v1/subscriptions?nsInstanceId=" + dummy_ns_id, format='json') + expected_response = self.test_single_subscription.copy() + expected_response["id"] = dummy_subscription_id + expected_response["filter"]["nsInstanceSubscriptionFilter"]["nsInstanceIds"] = \ + ns_instance_filter["nsInstanceIds"] + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual([expected_response], response.data) + + def test_get_subscriptions_with_unknown_ns_instance_id(self): + ns_instance_filter = { + "nsdIds": [], + "nsInstanceIds": [self.ns_instance_id], + "nsInstanceNames": [] + } + 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="['NsLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + ns_instance_filter=json.dumps(ns_instance_filter)).save() + response = self.client.get("/api/nslcm/v1/subscriptions?nsInstanceId=dummy", format='json') + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) + + def test_get_subscriptions_with_invalid_filter(self): + ns_instance_filter = { + "nsdIds": [], + "nsInstanceIds": [self.ns_instance_id], + "nsInstanceNames": [] + } + 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="['NsLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + ns_instance_filter=json.dumps(ns_instance_filter)).save() + response = self.client.get("/api/nslcm/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): + ns_instance_filter = { + "nsdIds": [], + "nsInstanceIds": [self.ns_instance_id], + "nsInstanceNames": [] + } + 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="['NsLcmOperationOccurrenceNotification']", + operation_types="['INSTANTIATE']", + operation_states="['STARTING']", + links=json.dumps(links), + ns_instance_filter=json.dumps(ns_instance_filter)).save() + dummy_ns_id = "584b35e2-b2a2-11e8-8e11-645106374fd3" + dummy_subscription_id = "947dcd2c-b2a2-11e8-b365-645106374fd4" + ns_instance_filter["nsInstanceIds"].append(dummy_ns_id) + SubscriptionModel(subscription_id=dummy_subscription_id, callback_uri="http://aurl.com", + auth_info="{}", notification_types="['NsLcmOperationOccurrenceNotification']", + operation_types="['SCALE']", + operation_states="['STARTING']", + links=json.dumps(links), + ns_instance_filter=json.dumps(ns_instance_filter)).save() + + response = self.client.get("/api/nslcm/v1/subscriptions?operationTypes=SCALE", format='json') + expected_response = self.test_single_subscription.copy() + expected_response["id"] = dummy_subscription_id + expected_response["filter"]["nsInstanceSubscriptionFilter"]["nsInstanceIds"] = \ + ns_instance_filter["nsInstanceIds"] + expected_response["filter"]["operationTypes"] = ["SCALE"] + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual([expected_response], response.data) diff --git a/lcm/ns/tests/test_subscribe_notification.py b/lcm/ns/tests/test_subscribe_notification.py new file mode 100644 index 00000000..6e3f84cf --- /dev/null +++ b/lcm/ns/tests/test_subscribe_notification.py @@ -0,0 +1,156 @@ +# Copyright (c) 2019, CMCC Technologies Co., Ltd. + +# 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/nslcm/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"]) + + @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": ["NsLcmOperationOccurrenceNotification"], + "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/nslcm/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"]) + + @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": ["NsLcmOperationOccurrenceNotification"], + "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/nslcm/v1/subscriptions", data=dummy_subscription, format='json') + self.assertEqual(500, response.status_code) + 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": ["NsIdentifierDeletionNotification"], + "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 ' + 'NsLcmOperationOccurrenceNotification' + } + response = self.client.post("/api/nslcm/v1/subscriptions", data=dummy_subscription, format='json') + self.assertEqual(500, response.status_code) + 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": ["NsLcmOperationOccurrenceNotification"], + "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/nslcm/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/nslcm/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/ns/urls.py b/lcm/ns/urls.py index 53c42498..441d522f 100644 --- a/lcm/ns/urls.py +++ b/lcm/ns/urls.py @@ -22,10 +22,11 @@ from lcm.ns.views.update_ns_view import NSUpdateView from lcm.ns.views.get_del_ns_view import NSDetailView from lcm.ns.views.inst_ns_post_deal_view import NSInstPostDealView from lcm.ns.views.scale_ns_views import NSManualScaleView +from lcm.ns.views.subscriptions_view import SubscriptionsView urlpatterns = [ # API will be deprecated in the future release - + url(r'^api/nslcm/v1/subscriptions$', SubscriptionsView.as_view()), url(r'^api/nslcm/v1/ns$', CreateNSView.as_view()), url(r'^api/nslcm/v1/ns/(?P[0-9a-zA-Z_-]+)/instantiate$', NSInstView.as_view()), url(r'^api/nslcm/v1/ns/(?P[0-9a-zA-Z_-]+)/terminate$', TerminateNSView.as_view()), @@ -35,7 +36,8 @@ urlpatterns = [ url(r'^api/nslcm/v1/ns/(?P[0-9a-zA-Z_-]+)/heal$', NSHealView.as_view()), # SOL005 URL API definition TODO - url(r'^api/nslcm/v1/ns/(?P[0-9a-zA-Z_-]+)/update$', NSUpdateView.as_view()) + url(r'^api/nslcm/v1/ns/(?P[0-9a-zA-Z_-]+)/update$', + NSUpdateView.as_view()) ] urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/lcm/ns/views/subscriptions_view.py b/lcm/ns/views/subscriptions_view.py new file mode 100644 index 00000000..a015839a --- /dev/null +++ b/lcm/ns/views/subscriptions_view.py @@ -0,0 +1,133 @@ +# Copyright (c) 2019, CMCC Technologies Co., Ltd. + +# 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.ns.biz.create_subscription import CreateSubscription +from lcm.ns.biz.query_subscription import QuerySubscription +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from lcm.ns.serializers.lccn_subscription_request import LccnSubscriptionRequestSerializer +from lcm.ns.serializers.lccn_subscription import LccnSubscriptionSerializer +from lcm.ns.serializers.lccn_subscriptions import LccnSubscriptionsSerializer +from lcm.ns.serializers.response import ProblemDetailsSerializer +from lcm.pub.exceptions import NSLCMException + +logger = logging.getLogger(__name__) +VALID_FILTERS = ["operationTypes", "operationStates", "notificationTypes", "nsInstanceId", + "nsComponentTypes", "lcmOpNameImpactingNsComponent", "lcmOpOccStatusImpactingNsComponent"] + + +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): + + @swagger_auto_schema( + request_body=LccnSubscriptionRequestSerializer(), + responses={ + status.HTTP_201_CREATED: LccnSubscriptionSerializer(), + status.HTTP_303_SEE_OTHER: ProblemDetailsSerializer(), + status.HTTP_500_INTERNAL_SERVER_ERROR: ProblemDetailsSerializer() + } + ) + 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 NSLCMException( + 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), + "nsInstanceSubscriptionFilter": json.loads(subscription.ns_instance_filter), + "nsComponentTypes": ast.literal_eval(subscription.ns_component_types), + "lcmOpNameImpactingNsComponent": ast.literal_eval(subscription. + lcm_opname_impacting_nscomponent), + "lcmOpOccStatusImpactingNsComponent": ast.literal_eval(subscription. + lcm_opoccstatus_impacting_nscomponent) + } + 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 NSLCMException(sub_resp_serializer.errors) + return Response(data=sub_resp_serializer.data, status=status.HTTP_201_CREATED) + except NSLCMException 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) + + @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 NSLCMException(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 NSLCMException 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) diff --git a/lcm/pub/database/models.py b/lcm/pub/database/models.py index b45867a5..a6694fa4 100644 --- a/lcm/pub/database/models.py +++ b/lcm/pub/database/models.py @@ -204,7 +204,8 @@ class VLInstModel(models.Model): ownertype = models.IntegerField(db_column='OWNERTYPE') ownerid = models.CharField(db_column='OWNERID', max_length=255) relatednetworkid = models.CharField(db_column='RELATEDNETWORKID', max_length=255, blank=True, null=True) - relatedsubnetworkid = models.CharField(db_column='RELATEDSUBNETWORKID', max_length=255, blank=True, null=True) + relatedsubnetworkid = models.CharField(db_column='RELATEDSUBNETWORKID', max_length=255, blank=True, + null=True) vltype = models.IntegerField(db_column='VLTYPE', default=0) vimid = models.CharField(db_column='VIMID', max_length=255) tenant = models.CharField(db_column='TENANT', max_length=255) @@ -338,11 +339,17 @@ class SubscriptionModel(models.Model): subscription_id = models.CharField(db_column='SUBSCRIPTIONID', max_length=255, primary_key=True) vnf_instance_filter = models.TextField(db_column='VNFINSTANCEFILTER', null=True) + ns_instance_filter = models.TextField(db_column='NSINSTANCEFILTER', 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) + ns_component_types = models.TextField(db_column='NSCOMPONENTTYPES', null=True) + lcm_opname_impacting_nscomponent = models.TextField(db_column='LCMOPNAMEIMPACTINGNSCOMPONENT', null=True) + lcm_opoccstatus_impacting_nscomponent = models.TextField(db_column='LCMOPOCCSTATUSIMPACTINGNSCOMPONENT', + null=True) callback_uri = models.CharField(db_column='CALLBACKURI', max_length=255) links = models.TextField(db_column='LINKS', max_length=20000) + auth_info = models.TextField(db_column='AUTHINFO', max_length=20000, blank=True, null=True) class PNFInstModel(models.Model): -- 2.16.6