diff --git a/api-ref/source/v1/parameters_vnflcm.yaml b/api-ref/source/v1/parameters_vnflcm.yaml index c573d6d1b..55eba26dc 100644 --- a/api-ref/source/v1/parameters_vnflcm.yaml +++ b/api-ref/source/v1/parameters_vnflcm.yaml @@ -631,6 +631,18 @@ filter_notification_types: in: body required: false type: string +filter_operation_states: + description: | + Match particular LCM operation state + values as reported in notifications of type + VnfLcmOperationOccurrenceNotification. + May be present if the "notificationTypes" + attribute contains the value + "VnfLcmOperationOccurrenceNotification" and + shall be absent otherwise. + in: body + required: false + type: string filter_operation_types: description: | Match particular VNF lifecycle operation types for @@ -1237,6 +1249,13 @@ vnf_instance_name: in: body required: false type: string +vnf_instance_subscription_filter: + description: | + Filter criteria to select VNF instances + about which to notify. + in: body + required: false + type: object vnf_instance_vim_connection_info: description: | Information about VIM connections to be used for managing the resources diff --git a/api-ref/source/v1/samples/vnflcm/create-subscription-request.json b/api-ref/source/v1/samples/vnflcm/create-subscription-request.json index bb7344baa..92a37abe2 100644 --- a/api-ref/source/v1/samples/vnflcm/create-subscription-request.json +++ b/api-ref/source/v1/samples/vnflcm/create-subscription-request.json @@ -2,7 +2,24 @@ "filter": { "notificationTypes": [ "VnfLcmOperationOccurrenceNotification" - ] + ], + "vnfInstanceSubscriptionFilter": { + "vnfdIds": [], + "vnfProductsFromProviders": { + "vnfProvider": "Vnf Provider 1", + "vnfProducts": [ + { + "vnfProductName": "Vnf Product 1", + "versions": [ + { + "vnfSoftwareVersion": "v1", + "vnfdVersions": ["vnfd.v1.1"] + } + ] + } + ] + } + } }, "callbackUri": "http://sample1.com/notification" } \ No newline at end of file diff --git a/api-ref/source/v1/samples/vnflcm/create-subscription-response.json b/api-ref/source/v1/samples/vnflcm/create-subscription-response.json index 6cb67914a..64a80dc27 100644 --- a/api-ref/source/v1/samples/vnflcm/create-subscription-response.json +++ b/api-ref/source/v1/samples/vnflcm/create-subscription-response.json @@ -1,14 +1,31 @@ -{ - "id": "76057f8e65ab37fb82d9382dfc3f3c8b", - "filter": { - "notificationTypes": [ - "VnfLcmOperationOccurrenceNotification" - ] - }, - "callbackUri": "http://sample1.com/notification", - "_links": { - "self": { - "href": "https://sample1.com/vnflcm/v1/subscriptions/76057f8e65ab37fb82d9382dfc3f3c8b" - } - } -} +{ + "id": "76057f8e65ab37fb82d9382dfc3f3c8b", + "filter": { + "vnfInstanceSubscriptionFilter": { + "vnfdIds": [], + "vnfProductsFromProviders": { + "vnfProvider": "Vnf Provider 1", + "vnfProducts": [ + { + "vnfProductName": "Vnf Product 1", + "versions": [ + { + "vnfSoftwareVersion": "v1", + "vnfdVersions": ["vnfd.v1.1"] + } + ] + } + ] + } + }, + "notificationTypes": [ + "VnfLcmOperationOccurrenceNotification" + ] + }, + "callbackUri": "http://sample1.com/notification", + "_links": { + "self": { + "href": "https://sample1.com/vnflcm/v1/subscriptions/76057f8e65ab37fb82d9382dfc3f3c8b" + } + } +} diff --git a/api-ref/source/v1/vnflcm.inc b/api-ref/source/v1/vnflcm.inc index ac5c5866b..b094822e0 100644 --- a/api-ref/source/v1/vnflcm.inc +++ b/api-ref/source/v1/vnflcm.inc @@ -1155,8 +1155,10 @@ Request Parameters .. rest_parameters:: parameters_vnflcm.yaml - filter: filter + - vnfInstanceSubscriptionFilter: vnf_instance_subscription_filter - notificationTypes: filter_notification_types - operationTypes: filter_operation_types + - operationStates: filter_operation_states - callbackUri : callback_uri - authentication: authentication - authType: authentication_auth_type diff --git a/tacker/api/schemas/vnf_lcm.py b/tacker/api/schemas/vnf_lcm.py index a74e495ed..ab40daf64 100644 --- a/tacker/api/schemas/vnf_lcm.py +++ b/tacker/api/schemas/vnf_lcm.py @@ -184,6 +184,109 @@ _vimConnectionInfo = { } } +_versions = { + 'type': 'array', + 'items': { + 'type': 'objects', + 'properties': { + 'vnfSoftwareVersion': {'type': 'string'}, + 'vnfdVersions': { + 'type': 'array', + 'items': {'type': 'string'} + } + }, + 'required': ['vnfSoftwareVersion'] + } +} + +_vnf_products = { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'vnfProductName': {'type': 'string'}, + 'versions': _versions + }, + 'required': ['vnfProductName'] + } +} + +_vnf_products_from_providers = { + 'type': 'object', + 'properties': { + 'type': 'object', + 'properties': { + 'vnfProvider': {'type': 'string'}, + 'vnfProducts': _vnf_products + } + }, + 'required': ['vnfProvider'] +} + +_lifecycle_change_notifications_filter = { + 'type': 'object', + 'properties': { + 'vnfInstanceSubscriptionFilter': { + 'type': 'object', + 'properties': { + 'vnfdIds': { + 'type': 'array', + 'items': parameter_types.identifier + }, + 'vnfProductsFromProviders': _vnf_products_from_providers, + 'vnfInstanceIds': { + 'type': 'array', + 'items': parameter_types.identifier + }, + 'vnfInstanceNames': { + 'type': 'array', + 'items': {'type': 'string'} + } + } + }, + 'notificationTypes': { + 'type': 'array', + 'items': { + 'type': 'string', + 'enum': [ + 'VnfLcmOperationOccurrenceNotification', + 'VnfIdentifierCreationNotification', + 'VnfIdentifierDeletionNotification'] + } + }, + 'operationTypes': { + 'type': 'array', + 'items': { + 'type': 'string', + 'enum': [ + 'INSTANTIATE', + 'SCALE', + 'SCALE_TO_LEVEL', + 'CHANGE_FLAVOUR', + 'TERMINATE', + 'HEAL', + 'OPERATE', + 'CHANGE_EXT_CONN', + 'MODIFY_INFO'] + } + }, + 'operationStates': { + 'type': 'array', + 'items': { + 'type': 'string', + 'enum': [ + 'STARTING', + 'PROCESSING', + 'COMPLETED', + 'FAILED_TEMP', + 'FAILED', + 'ROLLING_BACK', + 'ROLLED_BACK'] + } + } + } +} + create = { 'type': 'object', 'properties': { @@ -241,7 +344,7 @@ heal = { register_subscription = { 'type': 'object', 'properties': { - 'filter': parameter_types.keyvalue_pairs, + 'filter': _lifecycle_change_notifications_filter, 'callbackUri': {'type': 'string', 'maxLength': 255}, 'authentication': parameter_types.keyvalue_pairs, }, diff --git a/tacker/api/vnflcm/v1/controller.py b/tacker/api/vnflcm/v1/controller.py index cad527be5..dfd755f40 100644 --- a/tacker/api/vnflcm/v1/controller.py +++ b/tacker/api/vnflcm/v1/controller.py @@ -162,26 +162,6 @@ def check_vnf_status_and_error_point(action, status=None): class VnfLcmController(wsgi.Controller): - notification_type_list = ['VnfLcmOperationOccurrenceNotification', - 'VnfIdentifierCreationNotification', - 'VnfIdentifierDeletionNotification'] - operation_type_list = ['INSTANTIATE', - 'SCALE', - 'SCALE_TO_LEVEL', - 'CHANGE_FLAVOUR', - 'TERMINATE', - 'HEAL', - 'OPERATE', - 'CHANGE_EXT_CONN', - 'MODIFY_INFO'] - operation_state_list = ['STARTING', - 'PROCESSING', - 'COMPLETED', - 'FAILED_TEMP', - 'FAILED', - 'ROLLING_BACK', - 'ROLLED_BACK'] - _view_builder_class = vnf_lcm_view.ViewBuilder def __init__(self): @@ -904,28 +884,6 @@ class VnfLcmController(wsgi.Controller): @validation.schema(vnf_lcm.register_subscription) def register_subscription(self, request, body): subscription_request_data = body - if subscription_request_data.get('filter'): - # notificationTypes check - notification_types = subscription_request_data.get( - "filter").get("notificationTypes") - for notification_type in notification_types: - if notification_type not in self.notification_type_list: - msg = ( - _("notificationTypes value mismatch: %s") % - notification_type) - return self._make_problem_detail( - msg, 400, title='Bad Request') - - # operationTypes check - operation_types = subscription_request_data.get( - "filter").get("operationTypes") - for operation_type in operation_types: - if operation_type not in self.operation_type_list: - msg = ( - _("operationTypes value mismatch: %s") % - operation_type) - return self._make_problem_detail( - msg, 400, title='Bad Request') subscription_id = uuidutils.generate_uuid() diff --git a/tacker/common/utils.py b/tacker/common/utils.py index ae61963f9..ecb8112e6 100644 --- a/tacker/common/utils.py +++ b/tacker/common/utils.py @@ -337,6 +337,17 @@ def chunkiter(fp, chunk_size=65536): break +# TODO(esto.aln): Consider to move this function to +# convert_camelcase_to_snakecase(). We will consider the correct approach +# to modify the common function so as not to introduce degrade. +def convert_string_to_snakecase(name): + """Converts a string from camelCase to snake_case.""" + name_with_underscores = re.sub( + '(.)([A-Z][a-z]+)', r'\1_\2', name) + return re.sub('([a-z0-9])([A-Z])', r'\1_\2', + name_with_underscores).lower() + + def convert_camelcase_to_snakecase(request_data): """Converts dict keys or list of dict keys from camelCase to snake_case. @@ -347,17 +358,11 @@ def convert_camelcase_to_snakecase(request_data): :param request_data: dict with keys or list with items, in camelCase. """ - def convert(name): - name_with_underscores = re.sub( - '(.)([A-Z][a-z]+)', r'\1_\2', name) - return re.sub('([a-z0-9])([A-Z])', r'\1_\2', - name_with_underscores).lower() - if isinstance(request_data, dict): new_dict = {} for key, property_value in request_data.items(): property_value = convert_camelcase_to_snakecase(property_value) - underscore_joined = convert(key) + underscore_joined = convert_string_to_snakecase(key) new_dict[underscore_joined] = property_value return new_dict diff --git a/tacker/db/db_sqlalchemy/models.py b/tacker/db/db_sqlalchemy/models.py index 3ba1b38e8..b935359a0 100644 --- a/tacker/db/db_sqlalchemy/models.py +++ b/tacker/db/db_sqlalchemy/models.py @@ -287,6 +287,7 @@ class VnfLcmFilters(model_base.BASE): sa.ForeignKey('vnf_lcm_subscriptions.id'), nullable=False) filter = sa.Column(sa.JSON, nullable=False) + vnf_products_from_providers = sa.Column(sa.JSON, nullable=True) notification_types = sa.Column(sa.VARBINARY(255), nullable=True) notification_types_len = sa.Column(sa.Integer, nullable=True) operation_types = sa.Column( diff --git a/tacker/db/migration/alembic_migrations/versions/HEAD b/tacker/db/migration/alembic_migrations/versions/HEAD index 3a103665c..cb89ab5ca 100644 --- a/tacker/db/migration/alembic_migrations/versions/HEAD +++ b/tacker/db/migration/alembic_migrations/versions/HEAD @@ -1 +1 @@ -3adac34764da +c31f65e0d099 diff --git a/tacker/db/migration/alembic_migrations/versions/c31f65e0d099_add_columns_to_vnf_lcm_filter.py b/tacker/db/migration/alembic_migrations/versions/c31f65e0d099_add_columns_to_vnf_lcm_filter.py new file mode 100644 index 000000000..f00dc3a22 --- /dev/null +++ b/tacker/db/migration/alembic_migrations/versions/c31f65e0d099_add_columns_to_vnf_lcm_filter.py @@ -0,0 +1,137 @@ +# Copyright 2021 OpenStack Foundation +# +# 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. +# + +"""Add columns to vnf_lcm_filter + +Revision ID: c31f65e0d099 +Revises: 3adac34764da +Create Date: 2021-02-03 22:53:36.352774 + +""" + + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c31f65e0d099' +down_revision = '3adac34764da' + + +def upgrade(active_plugins=None, options=None): + sql_text_length = 65535 + + op.add_column( + 'vnf_lcm_filters', + sa.Column( + 'vnf_products_from_providers', sa.JSON())) + + op.add_column( + 'vnf_lcm_filters', + sa.Column( + 'operation_states', sa.TEXT(length=sql_text_length), + sa.Computed( + "json_unquote(json_extract(`filter`,'$.operationStates'))"))) + + op.add_column( + 'vnf_lcm_filters', + sa.Column( + 'operation_states_len', sa.Integer, + sa.Computed( + "ifnull(json_length(`operation_states`),0)"))) + + op.add_column( + 'vnf_lcm_filters', + sa.Column( + 'vnfd_ids', sa.TEXT(length=sql_text_length), + sa.Computed( + "json_unquote(json_extract(`filter`,'$.vnfdIds'))"))) + + op.add_column( + 'vnf_lcm_filters', + sa.Column( + 'vnfd_ids_len', sa.Integer, + sa.Computed( + "ifnull(json_length(`vnfd_ids`),0)"))) + + op.add_column( + 'vnf_lcm_filters', + sa.Column( + 'vnf_provider', sa.TEXT(length=sql_text_length), + sa.Computed( + "(ifnull(json_unquote(json_extract(" + "`vnf_products_from_providers`,'$.vnfProvider')),''))"))) + + op.add_column( + 'vnf_lcm_filters', + sa.Column( + 'vnf_product_name', sa.TEXT(length=sql_text_length), + sa.Computed( + "(ifnull(json_unquote(json_extract(" + "`vnf_products_from_providers`," + "'$.vnfProducts[0].vnfProductName')),''))"))) + + op.add_column( + 'vnf_lcm_filters', + sa.Column( + 'vnf_software_version', sa.TEXT(length=sql_text_length), + sa.Computed( + "(ifnull(json_unquote(json_extract(" + "`vnf_products_from_providers`,'$.vnfProducts[0]" + ".versions[0].vnfSoftwareVersion')),''))"))) + + op.add_column( + 'vnf_lcm_filters', + sa.Column( + 'vnfd_versions', sa.TEXT(length=sql_text_length), + sa.Computed( + "json_unquote(json_extract(`vnf_products_from_providers`," + "'$.vnfProducts[0].versions[0].vnfdVersions'))"))) + + op.add_column( + 'vnf_lcm_filters', + sa.Column( + 'vnfd_versions_len', sa.Integer, + sa.Computed( + "ifnull(json_length(`vnfd_versions`),0)"))) + + op.add_column( + 'vnf_lcm_filters', + sa.Column( + 'vnf_instance_ids', sa.TEXT(length=sql_text_length), + sa.Computed( + "json_unquote(json_extract(`filter`,'$.vnfInstanceIds'))"))) + + op.add_column( + 'vnf_lcm_filters', + sa.Column( + 'vnf_instance_ids_len', sa.Integer, + sa.Computed( + "ifnull(json_length(`vnf_instance_ids`),0)"))) + + op.add_column( + 'vnf_lcm_filters', + sa.Column( + 'vnf_instance_names', sa.TEXT(length=sql_text_length), + sa.Computed( + "json_unquote(json_extract(`filter`,'$.vnfInstanceNames'))"))) + + op.add_column( + 'vnf_lcm_filters', + sa.Column( + 'vnf_instance_names_len', sa.Integer, + sa.Computed( + "ifnull(json_length(`vnf_instance_names`),0)"))) diff --git a/tacker/objects/vnf_lcm_subscriptions.py b/tacker/objects/vnf_lcm_subscriptions.py index ce8f2f520..eeded3b9a 100644 --- a/tacker/objects/vnf_lcm_subscriptions.py +++ b/tacker/objects/vnf_lcm_subscriptions.py @@ -15,6 +15,7 @@ from oslo_utils import timeutils from sqlalchemy.sql import text from tacker.common import exceptions +from tacker.common.utils import convert_string_to_snakecase import tacker.conf from tacker.db import api as db_api from tacker.db.db_sqlalchemy import api @@ -28,6 +29,50 @@ LOG = logging.getLogger(__name__) CONF = tacker.conf.CONF +VNF_INSTANCE_SUBSCRIPTION_FILTER = [ + "vnfdIds", "vnfProvider", "vnfProductName", + "vnfSoftwareVersion", "vnfdVersions", "vnfInstanceIds", + "vnfInstanceNames" +] + + +VNF_INSTANCE_SUBSCRIPTION_FILTER_LISTS = [ + "vnfdIds", "vnfdVersions", "vnfInstanceIds", "vnfInstanceNames" +] + + +def _get_vnf_subscription_filter_values(vnf_subscription_filter): + vnfd_ids = vnf_subscription_filter.get('vnfdIds', []) + vnf_instance_ids = vnf_subscription_filter.get('vnfInstanceIds', []) + vnf_instance_names = vnf_subscription_filter.get('vnfInstanceNames', []) + + vnfd_products_from_providers = vnf_subscription_filter.get( + 'vnfProductsFromProviders', {}) + vnf_provider = vnfd_products_from_providers.get('vnfProvider', "") + vnf_products = vnfd_products_from_providers.get('vnfProducts', []) + + vnf_product_name = "" + vnf_software_version = "" + vnfd_versions = [] + if vnf_products: + vnf_product_name = vnf_products[0].get('vnfProductName', "") + versions = vnf_products[0].get('versions', []) + if versions: + vnf_software_version = versions[0].get('vnfSoftwareVersion', "") + vnfd_versions = versions[0].get('vnfdVersions', []) + + vnf_subscription_array = [ + {'vnfdIds': vnfd_ids}, + {'vnfInstanceIds': vnf_instance_ids}, + {'vnfInstanceNames': vnf_instance_names}, + {'vnfProvider': vnf_provider}, + {'vnfProductName': vnf_product_name}, + {'vnfSoftwareVersion': vnf_software_version}, + {'vnfdVersions': vnfd_versions}] + + return vnf_subscription_array + + def _make_list(value): if isinstance(value, list): res = "" @@ -165,7 +210,9 @@ def _get_by_subscriptionid(context, subscriptionsId): def _vnf_lcm_subscriptions_id_get(context, callbackUri, notification_type=None, - operation_type=None + operation_type=None, + operation_state=None, + vnf_instance_subscription_filter=None ): sql = ("select " @@ -175,6 +222,35 @@ def _vnf_lcm_subscriptions_id_get(context, "(select subscription_uuid from vnf_lcm_filters " "where ") + if vnf_instance_subscription_filter: + included_in_filter = [] + column_list = _get_vnf_subscription_filter_values( + vnf_instance_subscription_filter) + + for column in column_list: + for key in column: + if key in VNF_INSTANCE_SUBSCRIPTION_FILTER: + value = column[key] + if key in VNF_INSTANCE_SUBSCRIPTION_FILTER_LISTS: + value = _make_list(value) + else: + value = '"{}"'.format(value) + sql = (sql + " JSON_CONTAINS({}, '{}') and ".format( + convert_string_to_snakecase(key), + value + )) + included_in_filter.append(key) + + not_included_in_filter = list( + set(VNF_INSTANCE_SUBSCRIPTION_FILTER_LISTS) - + set(included_in_filter)) + + # items not being searched for is excluded by adding + # _len=0 to the sql query + for key in not_included_in_filter: + sql = sql + " {}_len=0 and ".format( + convert_string_to_snakecase(key)) + if notification_type: sql = (sql + " JSON_CONTAINS(notification_types, '" + _make_list(notification_type) + "') ") @@ -182,6 +258,13 @@ def _vnf_lcm_subscriptions_id_get(context, sql = sql + " notification_types_len=0 " sql = sql + "and " + if operation_state: + sql = (sql + " JSON_CONTAINS(operation_states, '" + + _make_list(operation_state) + "') ") + else: + sql = sql + " operation_states_len=0 " + sql = sql + "and " + if operation_type: sql = sql + " JSON_CONTAINS(operation_types, '" + \ _make_list(operation_type) + "') " @@ -200,14 +283,25 @@ def _vnf_lcm_subscriptions_id_get(context, return line except exceptions.NotFound: return '' + except Exception as exc: + LOG.error("SQL Error: %s" % str(exc)) + return '' def _add_filter_data(context, subscription_id, filter): with db_api.context_manager.writer.using(context): + vnf_instance_subscription_filter = \ + filter.get('vnfInstanceSubscriptionFilter') + + vnf_products_from_providers = \ + vnf_instance_subscription_filter.get( + 'vnfProductsFromProviders') new_entries = [] new_entries.append({"subscription_uuid": subscription_id, - "filter": filter}) + "filter": filter, + "vnf_products_from_providers": + vnf_products_from_providers}) context.session.execute( models.VnfLcmFilters.__table__.insert(None), @@ -236,12 +330,16 @@ def _vnf_lcm_subscriptions_create(context, values, filter): if filter: notification_type = filter.get('notificationTypes') operation_type = filter.get('operationTypes') + operation_state = filter.get('operationStates') + subscription_filter = filter.get('vnfInstanceSubscriptionFilter') vnf_lcm_subscriptions_id = _vnf_lcm_subscriptions_id_get( context, callbackUri, notification_type=notification_type, - operation_type=operation_type) + operation_type=operation_type, + operation_state=operation_state, + vnf_instance_subscription_filter=subscription_filter) if vnf_lcm_subscriptions_id: raise Exception("303" + vnf_lcm_subscriptions_id) diff --git a/tacker/tests/functional/sol/vnflcm/fake_vnflcm.py b/tacker/tests/functional/sol/vnflcm/fake_vnflcm.py index 418edc9a2..6fa57314b 100644 --- a/tacker/tests/functional/sol/vnflcm/fake_vnflcm.py +++ b/tacker/tests/functional/sol/vnflcm/fake_vnflcm.py @@ -30,6 +30,23 @@ class Subscription: """ return { "filter": { + "vnfInstanceSubscriptionFilter": { + "vnfdIds": ["b1bb0ce7-ebca-4fa7-95ed-4840d7000000"], + "vnfProductsFromProviders": { + "vnfProvider": "Company", + "vnfProducts": [ + { + "vnfProductName": "Sample VNF", + "versions": [ + { + "vnfSoftwareVersion": "1.0", + "vnfdVersions": ["1.0"] + } + ] + } + ] + } + }, "notificationTypes": [ "VnfLcmOperationOccurrenceNotification", "VnfIdentifierCreationNotification", @@ -42,7 +59,8 @@ class Subscription: "HEAL", "MODIFY_INFO", "CHANGE_EXT_CONN" - ] + ], + "operationStates": ["STARTING"] }, "callbackUri": callback_uri } diff --git a/tacker/tests/unit/vnflcm/test_controller.py b/tacker/tests/unit/vnflcm/test_controller.py index b21f1e8db..44c5a8022 100644 --- a/tacker/tests/unit/vnflcm/test_controller.py +++ b/tacker/tests/unit/vnflcm/test_controller.py @@ -24,6 +24,7 @@ import urllib import webob from webob import exc +from oslo_config import cfg from oslo_serialization import jsonutils from tacker.api.vnflcm.v1 import controller @@ -37,6 +38,7 @@ from tacker.extensions import vnfm from tacker.manager import TackerManager from tacker import objects from tacker.objects import fields +from tacker.objects import vnf_lcm_subscriptions as subscription_obj from tacker.tests import constants from tacker.tests.unit import base from tacker.tests.unit.db import utils @@ -3731,3 +3733,143 @@ class TestController(base.TestCase): self.assertEqual( "Can not find requested vnf: %s" % constants.INVALID_UUID, resp.json['itemNotFound']['message']) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @ddt.data('operationTypes', 'operationStates') + def test_register_subscription_operation_mismatch( + self, attribute, mock_get_service_plugins): + body = { + 'callbackUri': 'http://sample_callback_uri', + 'filter': { + 'notificationType': [ + 'VnfLcmOperationOccurrenceNotification'], + attribute: ['sample_operation'] + } + } + + req = fake_request.HTTPRequest.blank( + '/subscriptions') + req.body = jsonutils.dump_as_bytes(body) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + resp = req.get_response(self.app) + + self.assertEqual(http_client.BAD_REQUEST, resp.status_code) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + def test_register_subscription_operation_notification_mismatch( + self, mock_get_service_plugins): + body = { + 'callbackUri': 'http://sample_callback_uri', + 'filter': { + 'notificationTypes': ['sample_notification'], + } + } + + req = fake_request.HTTPRequest.blank( + '/subscriptions') + req.body = jsonutils.dump_as_bytes(body) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + resp = req.get_response(self.app) + + self.assertEqual(http_client.BAD_REQUEST, resp.status_code) + + @mock.patch.object(subscription_obj.LccnSubscriptionRequest, 'create') + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + def test_register_subscription_vnf_instance_subscription_filter( + self, mock_get_service_plugins, mock_create): + cfg.CONF.set_override('test_callback_uri', False, + group='vnf_lcm') + body = { + 'callbackUri': 'http://sample_callback_uri', + 'filter': { + 'notificationTypes': ['VnfLcmOperationOccurrenceNotification'], + 'vnfInstanceSubscriptionFilter': { + "vnfdIds": [], + "vnfProductsFromProviders": { + "vnfProvider": "Vnf Provider 1", + "vnfProducts": [ + { + "vnfProductName": "Vnf Product 1", + "versions": [ + { + "vnfSoftwareVersion": "v1", + "vnfdVersions": ["vnfd.v1.1"] + } + ] + } + ] + } + } + } + } + + req = fake_request.HTTPRequest.blank( + '/subscriptions') + req.body = jsonutils.dump_as_bytes(body) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + resp = req.get_response(self.app) + self.assertEqual(http_client.CREATED, resp.status_code) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + def test_register_subscription_vnf_instance_subscription_filter_error( + self, mock_get_service_plugins): + body = { + 'callbackUri': 'http://sample_callback_uri', + 'filter': { + 'notificationTypes': ['VnfLcmOperationOccurrenceNotification'], + 'vnfInstanceSubscriptionFilter': { + "vnfdIds": [], + "vnfProductsFromProviders": { + "vnfProducts": [ + { + "vnfProductName": "Vnf Product 1", + "versions": [ + { + "vnfSoftwareVersion": "v1", + "vnfdVersions": ["vnfd.v1.1"] + } + ] + } + ] + } + } + } + } + + req = fake_request.HTTPRequest.blank( + '/subscriptions') + req.body = jsonutils.dump_as_bytes(body) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + resp = req.get_response(self.app) + self.assertEqual(http_client.BAD_REQUEST, resp.status_code) + + @mock.patch.object(subscription_obj.LccnSubscriptionRequest, 'create') + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + def test_register_subscription( + self, mock_get_service_plugins, mock_save): + cfg.CONF.set_override('test_callback_uri', False, + group='vnf_lcm') + body = { + 'callbackUri': 'http://sample_callback_uri' + } + + req = fake_request.HTTPRequest.blank( + '/subscriptions') + req.body = jsonutils.dump_as_bytes(body) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + resp = req.get_response(self.app) + self.assertEqual(http_client.CREATED, resp.status_code)