aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryangyan <yangyanyj@chinamobile.com>2019-02-01 23:49:47 +0800
committeryangyan <yangyanyj@chinamobile.com>2019-02-01 23:49:59 +0800
commit8d1842182c9ef4ea15e848436df0ac5521d6fce5 (patch)
tree5151eecf6b3cc6c38fca28c22081e7b0cfde7950
parent607c637821bcc430a811ead7a379d0e87364ab4d (diff)
add sub api in lcm
Change-Id: I56ff6f78bd2eb33fe04ab57660a8138fb9cad140 Signed-off-by: yangyan <yangyanyj@chinamobile.com> Issue-ID: VFC-1253
-rw-r--r--lcm/ns/biz/create_subscription.py159
-rw-r--r--lcm/ns/biz/ns_terminate.py2
-rw-r--r--lcm/ns/biz/query_subscription.py75
-rw-r--r--lcm/ns/const.py130
-rw-r--r--lcm/ns/serializers/lccn_filter_data.py48
-rw-r--r--lcm/ns/serializers/lccn_subscription.py44
-rw-r--r--lcm/ns/serializers/lccn_subscription_request.py34
-rw-r--r--lcm/ns/serializers/lccn_subscriptions.py21
-rw-r--r--lcm/ns/serializers/link.py21
-rw-r--r--lcm/ns/serializers/ns_instance_subscription_filter.py42
-rw-r--r--lcm/ns/serializers/response.py26
-rw-r--r--lcm/ns/serializers/subscription_auth_data.py57
-rw-r--r--lcm/ns/tests/test_query_subscriptions.py184
-rw-r--r--lcm/ns/tests/test_subscribe_notification.py156
-rw-r--r--lcm/ns/urls.py6
-rw-r--r--lcm/ns/views/subscriptions_view.py133
-rw-r--r--lcm/pub/database/models.py9
17 files changed, 1141 insertions, 6 deletions
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<ns_instance_id>[0-9a-zA-Z_-]+)/instantiate$', NSInstView.as_view()),
url(r'^api/nslcm/v1/ns/(?P<ns_instance_id>[0-9a-zA-Z_-]+)/terminate$', TerminateNSView.as_view()),
@@ -35,7 +36,8 @@ urlpatterns = [
url(r'^api/nslcm/v1/ns/(?P<ns_instance_id>[0-9a-zA-Z_-]+)/heal$', NSHealView.as_view()),
# SOL005 URL API definition TODO
- url(r'^api/nslcm/v1/ns/(?P<ns_instance_id>[0-9a-zA-Z_-]+)/update$', NSUpdateView.as_view())
+ url(r'^api/nslcm/v1/ns/(?P<ns_instance_id>[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):