Merge "Support handling large queries for vnf packages"
This commit is contained in:
commit
8bddc176e9
|
@ -25,8 +25,10 @@ from zipfile import ZipFile
|
||||||
from glance_store import exceptions as store_exceptions
|
from glance_store import exceptions as store_exceptions
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
|
from oslo_utils import timeutils
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
from tacker._i18n import _
|
from tacker._i18n import _
|
||||||
|
@ -57,6 +59,7 @@ class VnfPkgmController(wsgi.Controller):
|
||||||
super(VnfPkgmController, self).__init__()
|
super(VnfPkgmController, self).__init__()
|
||||||
self.rpc_api = vnf_pkgm_rpc.VNFPackageRPCAPI()
|
self.rpc_api = vnf_pkgm_rpc.VNFPackageRPCAPI()
|
||||||
glance_store.initialize_glance_store()
|
glance_store.initialize_glance_store()
|
||||||
|
self._nextpages = {}
|
||||||
|
|
||||||
def _get_vnf_package(self, id, request):
|
def _get_vnf_package(self, id, request):
|
||||||
# check if id is of type uuid format
|
# check if id is of type uuid format
|
||||||
|
@ -72,6 +75,13 @@ class VnfPkgmController(wsgi.Controller):
|
||||||
raise webob.exc.HTTPNotFound(explanation=msg)
|
raise webob.exc.HTTPNotFound(explanation=msg)
|
||||||
return vnf_package
|
return vnf_package
|
||||||
|
|
||||||
|
def _delete_expired_nextpages(self, nextpages):
|
||||||
|
for k, v in nextpages.items():
|
||||||
|
if timeutils.is_older_than(v['created_time'],
|
||||||
|
CONF.vnf_package.nextpage_expiration_time):
|
||||||
|
LOG.debug('Old nextpages are deleted. id: %s' % k)
|
||||||
|
nextpages.pop(k)
|
||||||
|
|
||||||
@wsgi.response(http_client.CREATED)
|
@wsgi.response(http_client.CREATED)
|
||||||
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN))
|
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN))
|
||||||
@validation.schema(vnf_packages.create)
|
@validation.schema(vnf_packages.create)
|
||||||
|
@ -120,8 +130,9 @@ class VnfPkgmController(wsgi.Controller):
|
||||||
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN))
|
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN))
|
||||||
@validation.query_schema(vnf_packages.query_params_v1)
|
@validation.query_schema(vnf_packages.query_params_v1)
|
||||||
def index(self, request):
|
def index(self, request):
|
||||||
context = request.environ['tacker.context']
|
if 'tacker.context' in request.environ:
|
||||||
context.can(vnf_package_policies.VNFPKGM % 'index')
|
context = request.environ['tacker.context']
|
||||||
|
context.can(vnf_package_policies.VNFPKGM % 'index')
|
||||||
|
|
||||||
search_opts = {}
|
search_opts = {}
|
||||||
search_opts.update(request.GET)
|
search_opts.update(request.GET)
|
||||||
|
@ -139,6 +150,8 @@ class VnfPkgmController(wsgi.Controller):
|
||||||
fields = request.GET.get('fields')
|
fields = request.GET.get('fields')
|
||||||
exclude_fields = request.GET.get('exclude_fields')
|
exclude_fields = request.GET.get('exclude_fields')
|
||||||
filters = request.GET.get('filter')
|
filters = request.GET.get('filter')
|
||||||
|
nextpage = request.GET.get('nextpage_opaque_marker')
|
||||||
|
allrecords = request.GET.get('all_records')
|
||||||
if not (all_fields or fields or exclude_fields):
|
if not (all_fields or fields or exclude_fields):
|
||||||
exclude_default = True
|
exclude_default = True
|
||||||
|
|
||||||
|
@ -148,14 +161,45 @@ class VnfPkgmController(wsgi.Controller):
|
||||||
|
|
||||||
filters = self._view_builder.validate_filter(filters)
|
filters = self._view_builder.validate_filter(filters)
|
||||||
|
|
||||||
vnf_packages = vnf_package_obj.VnfPackagesList.get_by_filters(
|
results = []
|
||||||
request.context, read_deleted='no', filters=filters)
|
|
||||||
|
|
||||||
return self._view_builder.index(vnf_packages,
|
if allrecords != 'yes' and nextpage:
|
||||||
all_fields=all_fields,
|
self._delete_expired_nextpages(self._nextpages)
|
||||||
exclude_fields=exclude_fields,
|
|
||||||
fields=fields,
|
if nextpage in self._nextpages:
|
||||||
exclude_default=exclude_default)
|
results = self._nextpages.pop(
|
||||||
|
nextpage)['nextpage']
|
||||||
|
else:
|
||||||
|
vnf_packages = vnf_package_obj.VnfPackagesList.get_by_filters(
|
||||||
|
request.context, read_deleted='no', filters=filters)
|
||||||
|
|
||||||
|
results = self._view_builder.index(vnf_packages,
|
||||||
|
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(results) > CONF.vnf_package.vnf_package_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(
|
||||||
|
results[: CONF.vnf_package.vnf_package_num], default=str)
|
||||||
|
|
||||||
|
self._delete_expired_nextpages(self._nextpages)
|
||||||
|
|
||||||
|
remain = results[CONF.vnf_package.vnf_package_num:]
|
||||||
|
self._nextpages.update({nextpageid:
|
||||||
|
{'created_time': timeutils.utcnow(), 'nextpage': remain}})
|
||||||
|
else:
|
||||||
|
res.body = jsonutils.dump_as_bytes(results, default=str)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
@wsgi.response(http_client.NO_CONTENT)
|
@wsgi.response(http_client.NO_CONTENT)
|
||||||
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND,
|
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND,
|
||||||
|
|
|
@ -76,6 +76,12 @@ Related options:
|
||||||
'provider', 'product_name', 'software_version',
|
'provider', 'product_name', 'software_version',
|
||||||
'vnfm_info', 'flavour_id', 'flavour_description'],
|
'vnfm_info', 'flavour_id', 'flavour_description'],
|
||||||
help=_("List of del inputs from lower-vnfd")),
|
help=_("List of del inputs from lower-vnfd")),
|
||||||
|
cfg.IntOpt('vnf_package_num',
|
||||||
|
default=100,
|
||||||
|
help=_("Number of vnf_packages contained in 1 page")),
|
||||||
|
cfg.IntOpt('nextpage_expiration_time',
|
||||||
|
default=3600,
|
||||||
|
help=_("Expiration time (sec) for paging")),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,12 @@ import ddt
|
||||||
from http import client as http_client
|
from http import client as http_client
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
import urllib
|
import urllib
|
||||||
from webob import exc
|
from webob import exc
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
from tacker.api.vnfpkgm.v1 import controller
|
from tacker.api.vnfpkgm.v1 import controller
|
||||||
|
@ -127,7 +129,9 @@ class TestController(base.TestCase):
|
||||||
'checksum',
|
'checksum',
|
||||||
'userDefinedData',
|
'userDefinedData',
|
||||||
'additionalArtifacts'])
|
'additionalArtifacts'])
|
||||||
self.assertEqual(expected_result, res_dict)
|
self.assertEqual(
|
||||||
|
jsonutils.loads(jsonutils.dump_as_bytes(expected_result,
|
||||||
|
default=str)), res_dict.json)
|
||||||
|
|
||||||
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
||||||
def test_index_attribute_selector_all_fields(self, mock_vnf_list):
|
def test_index_attribute_selector_all_fields(self, mock_vnf_list):
|
||||||
|
@ -138,7 +142,9 @@ class TestController(base.TestCase):
|
||||||
mock_vnf_list.return_value = fakes.return_vnf_package_list()
|
mock_vnf_list.return_value = fakes.return_vnf_package_list()
|
||||||
res_dict = self.controller.index(req)
|
res_dict = self.controller.index(req)
|
||||||
expected_result = fakes.index_response()
|
expected_result = fakes.index_response()
|
||||||
self.assertEqual(expected_result, res_dict)
|
self.assertEqual(
|
||||||
|
jsonutils.loads(jsonutils.dump_as_bytes(expected_result,
|
||||||
|
default=str)), res_dict.json)
|
||||||
|
|
||||||
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
||||||
def test_index_attribute_selector_exclude_default(self, mock_vnf_list):
|
def test_index_attribute_selector_exclude_default(self, mock_vnf_list):
|
||||||
|
@ -154,7 +160,9 @@ class TestController(base.TestCase):
|
||||||
'checksum',
|
'checksum',
|
||||||
'userDefinedData',
|
'userDefinedData',
|
||||||
'additionalArtifacts'])
|
'additionalArtifacts'])
|
||||||
self.assertEqual(expected_result, res_dict)
|
self.assertEqual(
|
||||||
|
jsonutils.loads(jsonutils.dump_as_bytes(expected_result,
|
||||||
|
default=str)), res_dict.json)
|
||||||
|
|
||||||
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
||||||
@ddt.data(
|
@ddt.data(
|
||||||
|
@ -172,7 +180,9 @@ class TestController(base.TestCase):
|
||||||
res_dict = self.controller.index(req)
|
res_dict = self.controller.index(req)
|
||||||
remove_attrs = [params['exclude_fields']]
|
remove_attrs = [params['exclude_fields']]
|
||||||
expected_result = fakes.index_response(remove_attrs=remove_attrs)
|
expected_result = fakes.index_response(remove_attrs=remove_attrs)
|
||||||
self.assertEqual(expected_result, res_dict)
|
self.assertEqual(
|
||||||
|
jsonutils.loads(jsonutils.dump_as_bytes(expected_result,
|
||||||
|
default=str)), res_dict.json)
|
||||||
|
|
||||||
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
||||||
@ddt.data(
|
@ddt.data(
|
||||||
|
@ -199,7 +209,9 @@ class TestController(base.TestCase):
|
||||||
res_dict = self.controller.index(req)
|
res_dict = self.controller.index(req)
|
||||||
remove_attrs = [x for x in complex_attrs if x != params['fields']]
|
remove_attrs = [x for x in complex_attrs if x != params['fields']]
|
||||||
expected_result = fakes.index_response(remove_attrs=remove_attrs)
|
expected_result = fakes.index_response(remove_attrs=remove_attrs)
|
||||||
self.assertEqual(expected_result, res_dict)
|
self.assertEqual(
|
||||||
|
jsonutils.loads(jsonutils.dump_as_bytes(expected_result,
|
||||||
|
default=str)), res_dict.json)
|
||||||
|
|
||||||
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
||||||
def test_index_attribute_selector_user_defined_data_combination(self,
|
def test_index_attribute_selector_user_defined_data_combination(self,
|
||||||
|
@ -226,7 +238,9 @@ class TestController(base.TestCase):
|
||||||
'checksum',
|
'checksum',
|
||||||
'additionalArtifacts'],
|
'additionalArtifacts'],
|
||||||
vnf_package_updates=vnf_package_updates)
|
vnf_package_updates=vnf_package_updates)
|
||||||
self.assertEqual(expected_result, res_dict)
|
self.assertEqual(
|
||||||
|
jsonutils.loads(jsonutils.dump_as_bytes(expected_result,
|
||||||
|
default=str)), res_dict.json)
|
||||||
|
|
||||||
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
||||||
def test_index_attribute_selector_user_defined_data(self, mock_vnf_list):
|
def test_index_attribute_selector_user_defined_data(self, mock_vnf_list):
|
||||||
|
@ -238,7 +252,9 @@ class TestController(base.TestCase):
|
||||||
res_dict = self.controller.index(req)
|
res_dict = self.controller.index(req)
|
||||||
expected_result = fakes.index_response(remove_attrs=[
|
expected_result = fakes.index_response(remove_attrs=[
|
||||||
'checksum', 'softwareImages', 'additionalArtifacts'])
|
'checksum', 'softwareImages', 'additionalArtifacts'])
|
||||||
self.assertEqual(expected_result, res_dict)
|
self.assertEqual(
|
||||||
|
jsonutils.loads(jsonutils.dump_as_bytes(expected_result,
|
||||||
|
default=str)), res_dict.json)
|
||||||
|
|
||||||
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
||||||
def test_index_attribute_selector_nested_complex_attribute(self,
|
def test_index_attribute_selector_nested_complex_attribute(self,
|
||||||
|
@ -264,7 +280,9 @@ class TestController(base.TestCase):
|
||||||
expected_result = fakes.index_response(remove_attrs=[
|
expected_result = fakes.index_response(remove_attrs=[
|
||||||
'checksum', 'userDefinedData'],
|
'checksum', 'userDefinedData'],
|
||||||
vnf_package_updates=vnf_package_updates)
|
vnf_package_updates=vnf_package_updates)
|
||||||
self.assertEqual(expected_result, res_dict)
|
self.assertEqual(
|
||||||
|
jsonutils.loads(jsonutils.dump_as_bytes(expected_result,
|
||||||
|
default=str)), res_dict.json)
|
||||||
|
|
||||||
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
||||||
@ddt.data(
|
@ddt.data(
|
||||||
|
@ -303,7 +321,9 @@ class TestController(base.TestCase):
|
||||||
'checksum',
|
'checksum',
|
||||||
'userDefinedData',
|
'userDefinedData',
|
||||||
'additionalArtifacts'])
|
'additionalArtifacts'])
|
||||||
self.assertEqual(expected_result, res_dict)
|
self.assertEqual(
|
||||||
|
jsonutils.loads(jsonutils.dump_as_bytes(expected_result,
|
||||||
|
default=str)), res_dict.json)
|
||||||
|
|
||||||
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
||||||
def test_index_filter_combination(self, mock_vnf_list):
|
def test_index_filter_combination(self, mock_vnf_list):
|
||||||
|
@ -321,7 +341,9 @@ class TestController(base.TestCase):
|
||||||
'checksum',
|
'checksum',
|
||||||
'userDefinedData',
|
'userDefinedData',
|
||||||
'additionalArtifacts'])
|
'additionalArtifacts'])
|
||||||
self.assertEqual(expected_result, res_dict)
|
self.assertEqual(
|
||||||
|
jsonutils.loads(jsonutils.dump_as_bytes(expected_result,
|
||||||
|
default=str)), res_dict.json)
|
||||||
|
|
||||||
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
||||||
@ddt.data(
|
@ddt.data(
|
||||||
|
@ -368,7 +390,9 @@ class TestController(base.TestCase):
|
||||||
'checksum',
|
'checksum',
|
||||||
'userDefinedData',
|
'userDefinedData',
|
||||||
'additionalArtifacts'])
|
'additionalArtifacts'])
|
||||||
self.assertEqual(expected_result, res_dict)
|
self.assertEqual(
|
||||||
|
jsonutils.loads(jsonutils.dump_as_bytes(expected_result,
|
||||||
|
default=str)), res_dict.json)
|
||||||
|
|
||||||
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
||||||
@ddt.data(
|
@ddt.data(
|
||||||
|
@ -400,7 +424,9 @@ class TestController(base.TestCase):
|
||||||
'checksum',
|
'checksum',
|
||||||
'userDefinedData',
|
'userDefinedData',
|
||||||
'additionalArtifacts'])
|
'additionalArtifacts'])
|
||||||
self.assertEqual(expected_result, res_dict)
|
self.assertEqual(
|
||||||
|
jsonutils.loads(jsonutils.dump_as_bytes(expected_result,
|
||||||
|
default=str)), res_dict.json)
|
||||||
|
|
||||||
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
@mock.patch.object(VnfPackagesList, "get_by_filters")
|
||||||
@ddt.data(
|
@ddt.data(
|
||||||
|
@ -534,6 +560,52 @@ class TestController(base.TestCase):
|
||||||
self.assertRaises(tacker_exc.ValidationError, self.controller.index,
|
self.assertRaises(tacker_exc.ValidationError, self.controller.index,
|
||||||
req)
|
req)
|
||||||
|
|
||||||
|
@mock.patch.object(VnfPackagesList, "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_package_num', 1, group='vnf_package')
|
||||||
|
query = urllib.parse.urlencode(values['params'])
|
||||||
|
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
|
||||||
|
query)
|
||||||
|
mock_vnf_list.return_value = [
|
||||||
|
fakes.return_vnfpkg_obj(
|
||||||
|
vnfd_updates={'vnf_product_name': 'sample1'}),
|
||||||
|
fakes.return_vnfpkg_obj(
|
||||||
|
vnfd_updates={'vnf_product_name': 'sample2'}),
|
||||||
|
fakes.return_vnfpkg_obj(
|
||||||
|
vnfd_updates={'vnf_product_name': 'sample3'}),
|
||||||
|
fakes.return_vnfpkg_obj(
|
||||||
|
vnfd_updates={'vnf_product_name': 'sample4'})
|
||||||
|
]
|
||||||
|
expected_result = []
|
||||||
|
for name in values['result_names']:
|
||||||
|
expected_result += fakes.index_response(
|
||||||
|
remove_attrs=[
|
||||||
|
'softwareImages',
|
||||||
|
'checksum',
|
||||||
|
'userDefinedData',
|
||||||
|
'additionalArtifacts'],
|
||||||
|
vnf_package_updates={'vnfProductName': 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('/vnfpkgm/v1/vnf_packages?' +
|
||||||
|
query)
|
||||||
|
res_dict = self.controller.index(req)
|
||||||
|
self.assertEqual(
|
||||||
|
jsonutils.loads(jsonutils.dump_as_bytes(expected_result,
|
||||||
|
default=str)), res_dict.json)
|
||||||
|
|
||||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||||
@mock.patch.object(VNFPackageRPCAPI, "delete_vnf_package")
|
@mock.patch.object(VNFPackageRPCAPI, "delete_vnf_package")
|
||||||
def test_delete_with_204_status(self, mock_delete_rpc, mock_vnf_by_id):
|
def test_delete_with_204_status(self, mock_delete_rpc, mock_vnf_by_id):
|
||||||
|
|
Loading…
Reference in New Issue