Support handling large query results by ETSI NFV

This patch provides supporting handling large query results in a
response of target APIs.
The features to be added are like the following.

 - Paging query results according to ETSI NFV SOL013
 - Fetching entire records forcibly

Implements: blueprint paging-query-result
Change-Id: I587fd9a998032cc1d23b72d755c60fb7a859c7ee
This commit is contained in:
Koichi Edagawa 2022-01-24 10:17:29 +09:00
parent 17b03eb78f
commit 81dca79807
6 changed files with 358 additions and 85 deletions

View File

@ -0,0 +1,13 @@
---
features:
- |
Support handling large query results according to ETSI NFV SOL013.
This feature provides paged response regarding a query request of
target APIs. In addition to that, fetching entire records at once
becomes available.
issues:
- Regarding handling large query results according to ETSI NFV SOL013,
"vnfpkgm/v1/vnf_packages" API does not have the paging feature yet
because of longer time to be implemented than the other APIs. Since
there is less possibility to be paged in actual use case, the
implementation will be done in the next release.

View File

@ -192,6 +192,9 @@ class VnfLcmController(wsgi.Controller):
self._vnfm_plugin = manager.TackerManager.get_service_plugins()['VNFM']
self._view_builder_op_occ = vnf_op_occs_view.ViewBuilder()
self._view_builder_subscription = vnf_subscription_view.ViewBuilder()
self._nextpages_vnf_instances = {}
self._nextpages_lcm_op_occs = {}
self._nextpages_subscriptions = {}
def _get_vnf_instance_href(self, vnf_instance):
return '{}vnflcm/v1/vnf_instances/{}'.format(
@ -555,6 +558,13 @@ class VnfLcmController(wsgi.Controller):
return vnf_package_info[0]
def _delete_expired_nextpages(self, nextpages):
for k, v in list(nextpages.items()):
if timeutils.is_older_than(v['created_time'],
CONF.vnf_lcm.nextpage_expiration_time):
LOG.debug('Old nextpages are deleted. id: %s' % k)
nextpages.pop(k)
@wsgi.response(http_client.OK)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND))
def show(self, request, id):
@ -571,18 +581,54 @@ class VnfLcmController(wsgi.Controller):
@wsgi.response(http_client.OK)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.BAD_REQUEST))
@api_common.validate_supported_params({'filter'})
@api_common.validate_supported_params({'filter', 'nextpage_opaque_marker',
'all_records'})
def index(self, request):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'index')
if 'tacker.context' in request.environ:
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'index')
filters = request.GET.get('filter')
filters = self._view_builder.validate_filter(filters)
vnf_instances = objects.VnfInstanceList.get_by_filters(
request.context, filters=filters)
nextpage = request.GET.get('nextpage_opaque_marker')
allrecords = request.GET.get('all_records')
return self._view_builder.index(vnf_instances)
result = []
if allrecords != 'yes' and nextpage:
self._delete_expired_nextpages(self._nextpages_vnf_instances)
if nextpage in self._nextpages_vnf_instances:
result = self._nextpages_vnf_instances.pop(
nextpage)['nextpage']
else:
vnf_instances = objects.VnfInstanceList.get_by_filters(
request.context, filters=filters)
result = self._view_builder.index(vnf_instances)
res = webob.Response(content_type='application/json')
res.status_int = 200
if (allrecords != 'yes' and
len(result) > CONF.vnf_lcm.vnf_instance_num):
nextpageid = uuidutils.generate_uuid()
links = ('Link', '<%s?nextpage_opaque_marker=%s>; rel="next"' % (
request.path_url, nextpageid))
res.headerlist.append(links)
res.body = jsonutils.dump_as_bytes(
result[: CONF.vnf_lcm.vnf_instance_num])
self._delete_expired_nextpages(self._nextpages_vnf_instances)
remain = result[CONF.vnf_lcm.vnf_instance_num:]
self._nextpages_vnf_instances.update({nextpageid:
{'created_time': timeutils.utcnow(), 'nextpage': remain}})
else:
res.body = jsonutils.dump_as_bytes(result)
return res
@check_vnf_state(action="delete",
instantiation_state=[fields.VnfInstanceState.NOT_INSTANTIATED],
@ -1012,13 +1058,16 @@ class VnfLcmController(wsgi.Controller):
@wsgi.response(http_client.OK)
def subscription_list(self, request):
nextpage_opaque_marker = ""
nextpage_opaque_marker = None
paging = 1
filter_string = ""
ignore_nextpages = False
subscription_data = []
re_url = request.path_url
query_params = request.query_string
allrecords = request.GET.get('all_records')
if query_params:
query_params = parse.unquote(query_params)
query_param_list = query_params.split('&')
@ -1034,6 +1083,7 @@ class VnfLcmController(wsgi.Controller):
nextpage_opaque_marker = query_param_key_value[1]
if query_param_key_value[0] == 'page':
paging = int(query_param_key_value[1])
ignore_nextpages = True
if filter_string:
# check enumerations columns
@ -1061,51 +1111,58 @@ class VnfLcmController(wsgi.Controller):
return self._make_problem_detail(msg, 400,
title='Bad Request')
try:
filter_string_parsed = self._view_builder_subscription. \
validate_filter(filter_string)
if nextpage_opaque_marker:
start_index = paging - 1
else:
start_index = None
nextpage = nextpage_opaque_marker
if allrecords != 'yes' and not ignore_nextpages and nextpage:
self._delete_expired_nextpages(self._nextpages_subscriptions)
vnf_lcm_subscriptions, last = (
subscription_obj.LccnSubscriptionList.
get_by_filters(request.context,
read_deleted='no',
filters=filter_string_parsed,
nextpage_opaque_marker=start_index))
if nextpage in self._nextpages_subscriptions:
subscription_data = self._nextpages_subscriptions.pop(
nextpage)['nextpage']
else:
try:
filter_string_parsed = self._view_builder_subscription. \
validate_filter(filter_string)
if nextpage_opaque_marker:
start_index = paging - 1
else:
start_index = None
LOG.debug("vnf_lcm_subscriptions %s" % vnf_lcm_subscriptions)
subscription_data = self._view_builder_subscription. \
subscription_list(vnf_lcm_subscriptions)
LOG.debug("last %s" % last)
except Exception as e:
LOG.error(traceback.format_exc())
return self._make_problem_detail(
str(e), 500, title='Internal Server Error')
vnf_lcm_subscriptions = (
subscription_obj.LccnSubscriptionList.
get_by_filters(request.context,
read_deleted='no',
filters=filter_string_parsed,
nextpage_opaque_marker=start_index))
if subscription_data == 400:
msg = _("Number of records exceeds nextpage_opaque_marker")
return self._make_problem_detail(msg, 400, title='Bad Request')
LOG.debug("vnf_lcm_subscriptions %s" % vnf_lcm_subscriptions)
subscription_data = self._view_builder_subscription. \
subscription_list(vnf_lcm_subscriptions)
except Exception as e:
LOG.error(traceback.format_exc())
return self._make_problem_detail(
str(e), 500, title='Internal Server Error')
# make response
res = webob.Response(content_type='application/json')
res.body = jsonutils.dump_as_bytes(subscription_data)
res.status_int = 200
if nextpage_opaque_marker:
if not last:
ln = '<%s?page=%s>;rel="next"; title*="next chapter"' % (
re_url, paging + 1)
# Regarding the setting in http header related to
# nextpage control, RFC8288 and NFV-SOL013
# specifications have not been confirmed.
# Therefore, it is implemented by setting "page",
# which is a general control method of WebAPI,
# as "URI-Reference" of Link header.
links = ('Link', ln)
res.headerlist.append(links)
if (allrecords != 'yes' and not ignore_nextpages and
len(subscription_data) > CONF.vnf_lcm.subscription_num):
nextpageid = uuidutils.generate_uuid()
links = ('Link', '<%s?nextpage_opaque_marker=%s>; rel="next"' % (
request.path_url, nextpageid))
res.headerlist.append(links)
remain = subscription_data[CONF.vnf_lcm.subscription_num:]
subscription_data = (
subscription_data[: CONF.vnf_lcm.subscription_num])
self._delete_expired_nextpages(self._nextpages_subscriptions)
self._nextpages_subscriptions.update({nextpageid:
{'created_time': timeutils.utcnow(), 'nextpage': remain}})
res.body = jsonutils.dump_as_bytes(subscription_data)
LOG.debug("subscription_list res %s" % res)
return res
@ -1674,8 +1731,9 @@ class VnfLcmController(wsgi.Controller):
@wsgi.response(http_client.OK)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.BAD_REQUEST))
def list_lcm_op_occs(self, request):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'list_lcm_op_occs')
if 'tacker.context' in request.environ:
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'list_lcm_op_occs')
all_fields = request.GET.get('all_fields')
exclude_default = request.GET.get('exclude_default')
@ -1685,25 +1743,57 @@ class VnfLcmController(wsgi.Controller):
if not (all_fields or fields or exclude_fields):
exclude_default = True
self._view_builder_op_occ.validate_attribute_fields(
all_fields=all_fields, fields=fields,
exclude_fields=exclude_fields,
exclude_default=exclude_default)
nextpage = request.GET.get('nextpage_opaque_marker')
allrecords = request.GET.get('all_records')
filters = self._view_builder_op_occ.validate_filter(filters)
result = []
try:
vnf_lcm_op_occs = \
vnf_lcm_op_occs_obj.VnfLcmOpOccList.get_by_filters(
request.context, read_deleted='no', filters=filters)
except Exception as e:
LOG.exception(traceback.format_exc())
return self._make_problem_detail(
str(e), 500, title='Internal Server Error')
if allrecords != 'yes' and nextpage:
self._delete_expired_nextpages(self._nextpages_lcm_op_occs)
return self._view_builder_op_occ.index(request, vnf_lcm_op_occs,
all_fields=all_fields, exclude_fields=exclude_fields,
fields=fields, exclude_default=exclude_default)
if nextpage in self._nextpages_lcm_op_occs:
result = self._nextpages_lcm_op_occs.pop(nextpage)['nextpage']
else:
self._view_builder_op_occ.validate_attribute_fields(
all_fields=all_fields, fields=fields,
exclude_fields=exclude_fields,
exclude_default=exclude_default)
filters = self._view_builder_op_occ.validate_filter(filters)
try:
vnf_lcm_op_occs = (
vnf_lcm_op_occs_obj.VnfLcmOpOccList.get_by_filters(
request.context, read_deleted='no', filters=filters))
except Exception as e:
LOG.exception(traceback.format_exc())
return self._make_problem_detail(
str(e), 500, title='Internal Server Error')
result = self._view_builder_op_occ.index(request, vnf_lcm_op_occs,
all_fields=all_fields, exclude_fields=exclude_fields,
fields=fields, exclude_default=exclude_default)
res = webob.Response(content_type='application/json')
res.status_int = 200
if allrecords != 'yes' and len(result) > CONF.vnf_lcm.lcm_op_occ_num:
nextpageid = uuidutils.generate_uuid()
links = ('Link', '<%s?nextpage_opaque_marker=%s>; rel="next"' % (
request.path_url, nextpageid))
res.headerlist.append(links)
res.body = jsonutils.dump_as_bytes(
result[: CONF.vnf_lcm.lcm_op_occ_num])
self._delete_expired_nextpages(self._nextpages_lcm_op_occs)
remain = result[CONF.vnf_lcm.lcm_op_occ_num:]
self._nextpages_lcm_op_occs.update({nextpageid:
{'created_time': timeutils.utcnow(), 'nextpage': remain}})
else:
res.body = jsonutils.dump_as_bytes(result)
return res
def _make_problem_detail(
self,

View File

@ -47,7 +47,19 @@ OPTS = [
cfg.BoolOpt(
'verify_notification_ssl',
default=True,
help="Verify the certificate to send notification by ssl")]
help="Verify the certificate to send notification by ssl"),
cfg.IntOpt(
'lcm_op_occ_num',
default=100,
help="Number of lcm_op_occs contained in 1 page"),
cfg.IntOpt(
'vnf_instance_num',
default=100,
help="Number of vnf_instances contained in 1 page"),
cfg.IntOpt(
'nextpage_expiration_time',
default=3600,
help="Expiration time (sec) for paging")]
vnf_lcm_group = cfg.OptGroup('vnf_lcm',
title='vnf_lcm options',

View File

@ -457,20 +457,14 @@ def _make_subscription_list(context, subscription_list, db_subscription_list,
subscription_cls = LccnSubscription
subscription_list.objects = []
cnt = 0
last_flg = True
for db_subscription in db_subscription_list:
cnt = cnt + 1
if cnt == CONF.vnf_lcm.subscription_num + 1:
last_flg = False
break
subscription_obj = subscription_cls._from_db_object(
context, subscription_cls(context), db_subscription,
expected_attrs=expected_attrs)
subscription_list.objects.append(subscription_obj)
subscription_list.obj_reset_changes()
return subscription_list, last_flg
return subscription_list
@base.TackerObjectRegistry.register

View File

@ -1682,8 +1682,12 @@ def fake_vnf_lcm_op_occs():
return vnf_lcm_op_occs
def return_vnf_lcm_opoccs_obj():
def return_vnf_lcm_opoccs_obj(**updates):
vnf_lcm_op_occs = fake_vnf_lcm_op_occs()
if updates:
vnf_lcm_op_occs.update(**updates)
obj = objects.VnfLcmOpOcc(**vnf_lcm_op_occs)
return obj

View File

@ -19,6 +19,7 @@ import ddt
from http import client as http_client
import json
import os
import re
from unittest import mock
import urllib
import webob
@ -1697,14 +1698,14 @@ class TestController(base.TestCase):
expected_result = [fakes.fake_vnf_instance_response(),
fakes.fake_vnf_instance_response(
fields.VnfInstanceState.INSTANTIATED)]
self.assertEqual(expected_result, resp)
self.assertEqual(expected_result, resp.json)
@mock.patch.object(objects.VnfInstanceList, "get_by_filters")
def test_index_empty_response(self, mock_vnf_list):
req = fake_request.HTTPRequest.blank('/vnf_instances')
mock_vnf_list.return_value = []
resp = self.controller.index(req)
self.assertEqual([], resp)
self.assertEqual([], resp.json)
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM':
@ -1859,7 +1860,7 @@ class TestController(base.TestCase):
expected_result = [fakes.fake_vnf_instance_response(),
fakes.fake_vnf_instance_response(
fields.VnfInstanceState.INSTANTIATED)]
self.assertEqual(expected_result, res_dict)
self.assertEqual(expected_result, res_dict.json)
@mock.patch.object(objects.VnfInstanceList, "get_by_filters")
def test_index_filter_combination(self, mock_vnf_list):
@ -1882,7 +1883,7 @@ class TestController(base.TestCase):
expected_result = [fakes.fake_vnf_instance_response(),
fakes.fake_vnf_instance_response(
fields.VnfInstanceState.INSTANTIATED)]
self.assertEqual(expected_result, res_dict)
self.assertEqual(expected_result, res_dict.json)
@mock.patch.object(objects.VnfInstanceList, "get_by_filters")
@ddt.data(
@ -1935,7 +1936,7 @@ class TestController(base.TestCase):
expected_result = [fakes.fake_vnf_instance_response(),
fakes.fake_vnf_instance_response(
fields.VnfInstanceState.INSTANTIATED)]
self.assertEqual(expected_result, res_dict)
self.assertEqual(expected_result, res_dict.json)
@mock.patch.object(objects.VnfInstanceList, "get_by_filters")
@ddt.data(
@ -2017,6 +2018,46 @@ class TestController(base.TestCase):
self.assertRaises(exceptions.ValidationError,
self.controller.index, req)
@mock.patch.object(objects.VnfInstanceList, "get_by_filters")
@ddt.data(
{'params': {'all_records': 'yes'},
'result_names': ['sample1', 'sample2', 'sample3', 'sample4']},
{'params': {'all_records': 'yes', 'nextpage_opaque_marker': 'abc'},
'result_names': ['sample1', 'sample2', 'sample3', 'sample4']},
{'params': {'nextpage_opaque_marker': 'abc'},
'result_names': []},
{'params': {},
'result_names': ['sample2']}
)
def test_index_paging(self, values, mock_vnf_list):
cfg.CONF.set_override('vnf_instance_num', 1, group='vnf_lcm')
query = urllib.parse.urlencode(values['params'])
req = fake_request.HTTPRequest.blank(
'/vnflcm/v1/vnf_instances?' + query)
mock_vnf_list.return_value = [
fakes.return_vnf_instance(**{'vnf_instance_name': 'sample1'}),
fakes.return_vnf_instance(**{'vnf_instance_name': 'sample2'}),
fakes.return_vnf_instance(**{'vnf_instance_name': 'sample3'}),
fakes.return_vnf_instance(**{'vnf_instance_name': 'sample4'})
]
expected_result = []
for name in values['result_names']:
expected_result.append(fakes.fake_vnf_instance_response(
**{'vnfInstanceName': name}))
res_dict = self.controller.index(req)
if 'Link' in res_dict.headers:
next_url = re.findall('<(.*)>', res_dict.headers['Link'])[0]
query = urllib.parse.urlparse(next_url).query
req = fake_request.HTTPRequest.blank(
'/vnflcm/v1/vnf_instances?' + query)
res_dict = self.controller.index(req)
self.assertEqual(expected_result, res_dict.json)
@mock.patch.object(objects.VnfInstanceList, "get_by_filters")
@ddt.data(
{'attribute_not_exist': 'some_value'},
@ -2024,7 +2065,6 @@ class TestController(base.TestCase):
{'fields': {}},
{'exclude_fields': {}},
{'exclude_default': {}},
{'nextpage_opaque_marker': 1},
{'attribute_not_exist': 'some_value', 'filter': {}},
{'attribute_not_exist': 'some_value', 'fields': {}}
)
@ -2044,7 +2084,6 @@ class TestController(base.TestCase):
{'fields': {}},
{'exclude_fields': {}},
{'exclude_default': {}},
{'nextpage_opaque_marker': 1},
{'attribute_not_exist': 'some_value', 'filter': {}},
{'attribute_not_exist': 'some_value', 'fields': {}}
)
@ -3760,9 +3799,61 @@ class TestController(base.TestCase):
expected_result = fakes.index_response(
remove_attrs=complex_attributes)
mock_op_occ_list.return_value = vnf_lcm_op_occ
res_dict = self.controller.list_lcm_op_occs(req)
resp = self.controller.list_lcm_op_occs(req)
self.assertEqual(expected_result, res_dict)
self.assertEqual(
jsonutils.loads(jsonutils.dump_as_bytes(expected_result)),
resp.json)
@mock.patch.object(objects.VnfLcmOpOccList, "get_by_filters")
@ddt.data(
{'params': {'all_records': 'yes'},
'result_names': ['INSTANTIATE', 'SCALE', 'HEAL', 'TERMINATE']},
{'params': {'all_records': 'yes', 'nextpage_opaque_marker': 'abc'},
'result_names': ['INSTANTIATE', 'SCALE', 'HEAL', 'TERMINATE']},
{'params': {'nextpage_opaque_marker': 'abc'},
'result_names': []},
{'params': {},
'result_names': ['SCALE']}
)
def test_op_occ_list_paging(self, values, mock_op_occ_list):
cfg.CONF.set_override('lcm_op_occ_num', 1, group='vnf_lcm')
query = urllib.parse.urlencode(values['params'])
req = fake_request.HTTPRequest.blank(
'/vnflcm/v1/vnf_lcm_op_occs?' + query)
complex_attributes = [
'error',
'resourceChanges',
'operationParams',
'changedInfo']
vnf_lcm_op_occ = [
fakes.return_vnf_lcm_opoccs_obj(**{'operation': 'INSTANTIATE'}),
fakes.return_vnf_lcm_opoccs_obj(**{'operation': 'SCALE'}),
fakes.return_vnf_lcm_opoccs_obj(**{'operation': 'HEAL'}),
fakes.return_vnf_lcm_opoccs_obj(**{'operation': 'TERMINATE'})
]
expected_result = []
for name in values['result_names']:
expected_result += fakes.index_response(
remove_attrs=complex_attributes,
vnf_lcm_op_occs_updates={'operation': name})
mock_op_occ_list.return_value = vnf_lcm_op_occ
resp = self.controller.list_lcm_op_occs(req)
if 'Link' in resp.headers:
next_url = re.findall('<(.*)>', resp.headers['Link'])[0]
query = urllib.parse.urlparse(next_url).query
req = fake_request.HTTPRequest.blank(
'/vnflcm/v1/vnf_lcm_op_occs?' + query)
resp = self.controller.list_lcm_op_occs(req)
self.assertEqual(
jsonutils.loads(jsonutils.dump_as_bytes(expected_result)),
resp.json)
@mock.patch.object(objects.VnfLcmOpOccList, "get_by_filters")
@ddt.data(
@ -3797,7 +3888,9 @@ class TestController(base.TestCase):
mock_op_occ_list.return_value = vnf_lcm_op_occ
res_dict = self.controller.list_lcm_op_occs(req)
self.assertEqual(expected_result, res_dict)
self.assertEqual(
jsonutils.loads(jsonutils.dump_as_bytes(expected_result)),
res_dict.json)
@mock.patch.object(objects.VnfLcmOpOccList, "get_by_filters")
def test_op_occ_filter_attributes_invalid_filter(self, mock_op_occ_list):
@ -3822,7 +3915,9 @@ class TestController(base.TestCase):
mock_op_occ_list.return_value = vnf_lcm_op_occ
res_dict = self.controller.list_lcm_op_occs(req)
self.assertEqual(expected_result, res_dict)
self.assertEqual(
jsonutils.loads(jsonutils.dump_as_bytes(expected_result)),
res_dict.json)
@mock.patch.object(objects.VnfLcmOpOccList, "get_by_filters")
@ddt.data(
@ -3850,7 +3945,9 @@ class TestController(base.TestCase):
expected_result = fakes.index_response(remove_attrs=remove_attributes)
mock_op_occ_list.return_value = vnf_lcm_op_occ
res_dict = self.controller.list_lcm_op_occs(req)
self.assertEqual(expected_result, res_dict)
self.assertEqual(
jsonutils.loads(jsonutils.dump_as_bytes(expected_result)),
res_dict.json)
@mock.patch.object(objects.VnfLcmOpOccList, "get_by_filters")
@ddt.data(
@ -3871,7 +3968,9 @@ class TestController(base.TestCase):
expected_result = fakes.index_response(remove_attrs=remove_attributes)
mock_op_occ_list.return_value = vnf_lcm_op_occ
res_dict = self.controller.list_lcm_op_occs(req)
self.assertEqual(expected_result, res_dict)
self.assertEqual(
jsonutils.loads(jsonutils.dump_as_bytes(expected_result)),
res_dict.json)
@mock.patch.object(objects.VnfLcmOpOccList, "get_by_filters")
def test_op_occ_attribute_selector_fields_error(self, mock_op_occ_list):
@ -4326,6 +4425,67 @@ class TestController(base.TestCase):
resp = req.get_response(self.app)
self.assertEqual(500, resp.status_code)
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM':
test_nfvo_plugin.FakeVNFMPlugin()})
@mock.patch.object(vnf_subscription_view.ViewBuilder,
"subscription_list")
@mock.patch.object(vnf_subscription_view.ViewBuilder,
"validate_filter")
@mock.patch.object(objects.LccnSubscriptionList,
"get_by_filters")
@ddt.data(
{'params': {'all_records': 'yes'},
'result_names': ['subscription_id_1', 'subscription_id_2',
'subscription_id_3', 'subscription_id_4']},
{'params': {'all_records': 'yes', 'nextpage_opaque_marker': 'abc'},
'result_names': ['subscription_id_1', 'subscription_id_2',
'subscription_id_3', 'subscription_id_4']},
{'params': {'nextpage_opaque_marker': 'abc'},
'result_names': []},
{'params': {},
'result_names': ['subscription_id_2']}
)
def test_subscription_list_paging(self,
values,
mock_subscription_list,
mock_subscription_filter,
mock_subscription_view,
mock_get_service_plugins):
mock_subscription_filter.return_value = None
last = True
cfg.CONF.set_override('subscription_num', 1, group='vnf_lcm')
query = urllib.parse.urlencode(values['params'])
req = fake_request.HTTPRequest.blank('/subscriptions?' + query)
req.method = 'GET'
subscription_list = [
fakes.return_subscription_object(
**{'id': uuidsentinel.subscription_id_1}),
fakes.return_subscription_object(
**{'id': uuidsentinel.subscription_id_2}),
fakes.return_subscription_object(
**{'id': uuidsentinel.subscription_id_3}),
fakes.return_subscription_object(
**{'id': uuidsentinel.subscription_id_4})
]
mock_subscription_list.return_value = [subscription_list, last]
mock_subscription_view.return_value = subscription_list
resp = self.controller.subscription_list(req)
if 'Link' in resp.headers:
next_url = re.findall('<(.*)>', resp.headers['Link'])[0]
query = urllib.parse.urlparse(next_url).query
req = fake_request.HTTPRequest.blank('/subscriptions?' + query)
resp = self.controller.subscription_list(req)
expected_result = []
for name in values['result_names']:
expected_result.append(fakes.return_subscription_object(
**{'id': eval('uuidsentinel.' + name)}))
self.assertEqual(200, resp.status_code)
self.assertEqual(expected_result, resp.json)
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM':
test_nfvo_plugin.FakeVNFMPlugin()})