Add pagination support to Qos specs
In Liberty release, we have added pagination to backups and snapshot. There are still some work that hasn't been done yet. This patch add pagination support to Qos specs. APIImpact DocImpact Implements: blueprint add-pagination-to-other-resource Change-Id: I1965c8be6b4415ff99fb50e05e77f791f3ff5942
This commit is contained in:
parent
ae2ec9bf69
commit
31fb64d694
@ -20,6 +20,7 @@ from oslo_utils import strutils
|
||||
import six
|
||||
import webob
|
||||
|
||||
from cinder.api import common
|
||||
from cinder.api import extensions
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.views import qos_specs as view_qos_specs
|
||||
@ -115,7 +116,20 @@ class QoSSpecsController(wsgi.Controller):
|
||||
"""Returns the list of qos_specs."""
|
||||
context = req.environ['cinder.context']
|
||||
authorize(context)
|
||||
specs = qos_specs.get_all_specs(context)
|
||||
|
||||
params = req.params.copy()
|
||||
|
||||
marker, limit, offset = common.get_pagination_params(params)
|
||||
sort_keys, sort_dirs = common.get_sort_params(params)
|
||||
filters = params
|
||||
allowed_search_options = ('id', 'name', 'consumer')
|
||||
utils.remove_invalid_filter_options(context, filters,
|
||||
allowed_search_options)
|
||||
|
||||
specs = qos_specs.get_all_specs(context, filters=filters,
|
||||
marker=marker, limit=limit,
|
||||
offset=offset, sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs)
|
||||
return self._view_builder.summary_list(req, specs)
|
||||
|
||||
@wsgi.serializers(xml=QoSSpecsTemplate)
|
||||
|
@ -24,15 +24,15 @@ LOG = logging.getLogger(__name__)
|
||||
class ViewBuilder(common.ViewBuilder):
|
||||
"""Model QoS specs API responses as a python dictionary."""
|
||||
|
||||
_collection_name = "qos_specs"
|
||||
_collection_name = "qos-specs"
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize view builder."""
|
||||
super(ViewBuilder, self).__init__()
|
||||
|
||||
def summary_list(self, request, qos_specs):
|
||||
def summary_list(self, request, qos_specs, qos_count=None):
|
||||
"""Show a list of qos_specs without many details."""
|
||||
return self._list_view(self.detail, request, qos_specs)
|
||||
return self._list_view(self.detail, request, qos_specs, qos_count)
|
||||
|
||||
def summary(self, request, qos_spec):
|
||||
"""Generic, non-detailed view of a qos_specs."""
|
||||
@ -57,9 +57,14 @@ class ViewBuilder(common.ViewBuilder):
|
||||
'qos_associations': associates
|
||||
}
|
||||
|
||||
def _list_view(self, func, request, qos_specs):
|
||||
def _list_view(self, func, request, qos_specs, qos_count=None):
|
||||
"""Provide a view for a list of qos_specs."""
|
||||
specs_list = [func(request, specs)['qos_specs'] for specs in qos_specs]
|
||||
specs_links = self._get_collection_links(request, qos_specs,
|
||||
self._collection_name,
|
||||
qos_count)
|
||||
specs_dict = dict(qos_specs=specs_list)
|
||||
if specs_links:
|
||||
specs_dict['qos_specs_links'] = specs_links
|
||||
|
||||
return specs_dict
|
||||
|
@ -591,9 +591,12 @@ def qos_specs_get(context, qos_specs_id):
|
||||
return IMPL.qos_specs_get(context, qos_specs_id)
|
||||
|
||||
|
||||
def qos_specs_get_all(context, inactive=False, filters=None):
|
||||
def qos_specs_get_all(context, filters=None, marker=None, limit=None,
|
||||
offset=None, sort_keys=None, sort_dirs=None):
|
||||
"""Get all qos_specs."""
|
||||
return IMPL.qos_specs_get_all(context, inactive, filters)
|
||||
return IMPL.qos_specs_get_all(context, filters=filters, marker=marker,
|
||||
limit=limit, offset=offset,
|
||||
sort_keys=sort_keys, sort_dirs=sort_dirs)
|
||||
|
||||
|
||||
def qos_specs_get_by_name(context, name):
|
||||
|
@ -1591,13 +1591,13 @@ def _generate_paginate_query(context, session, marker, limit, sort_keys,
|
||||
if query is None:
|
||||
return None
|
||||
|
||||
marker_volume = None
|
||||
marker_object = None
|
||||
if marker is not None:
|
||||
marker_volume = get(context, marker, session)
|
||||
marker_object = get(context, marker, session)
|
||||
|
||||
return sqlalchemyutils.paginate_query(query, paginate_type, limit,
|
||||
sort_keys,
|
||||
marker=marker_volume,
|
||||
marker=marker_object,
|
||||
sort_dirs=sort_dirs,
|
||||
offset=offset)
|
||||
|
||||
@ -3002,7 +3002,8 @@ def qos_specs_get(context, qos_specs_id, inactive=False):
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def qos_specs_get_all(context, inactive=False, filters=None):
|
||||
def qos_specs_get_all(context, filters=None, marker=None, limit=None,
|
||||
offset=None, sort_keys=None, sort_dirs=None):
|
||||
"""Returns a list of all qos_specs.
|
||||
|
||||
Results is like:
|
||||
@ -3028,15 +3029,48 @@ def qos_specs_get_all(context, inactive=False, filters=None):
|
||||
},
|
||||
]
|
||||
"""
|
||||
filters = filters or {}
|
||||
# TODO(zhiteng) Add filters for 'consumer'
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
# Generate the query
|
||||
query = _generate_paginate_query(context, session, marker, limit,
|
||||
sort_keys, sort_dirs, filters,
|
||||
offset, models.QualityOfServiceSpecs)
|
||||
# No Qos specs would match, return empty list
|
||||
if query is None:
|
||||
return []
|
||||
rows = query.all()
|
||||
return _dict_with_qos_specs(rows)
|
||||
|
||||
read_deleted = "yes" if inactive else "no"
|
||||
|
||||
@require_admin_context
|
||||
def _qos_specs_get_query(context, session):
|
||||
rows = model_query(context, models.QualityOfServiceSpecs,
|
||||
read_deleted=read_deleted). \
|
||||
options(joinedload_all('specs')).all()
|
||||
session=session,
|
||||
read_deleted='no').\
|
||||
options(joinedload_all('specs')).filter_by(key='QoS_Specs_Name')
|
||||
return rows
|
||||
|
||||
return _dict_with_qos_specs(rows)
|
||||
|
||||
def _process_qos_specs_filters(query, filters):
|
||||
if filters:
|
||||
# Ensure that filters' keys exist on the model
|
||||
if not is_valid_model_filters(models.QualityOfServiceSpecs, filters):
|
||||
return
|
||||
query = query.filter_by(**filters)
|
||||
return query
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def _qos_specs_get(context, qos_spec_id, session=None):
|
||||
result = model_query(context, models.QualityOfServiceSpecs,
|
||||
session=session,
|
||||
read_deleted='no').\
|
||||
filter_by(id=qos_spec_id).filter_by(key='QoS_Specs_Name').first()
|
||||
|
||||
if not result:
|
||||
raise exception.QoSSpecsNotFound(specs_id=qos_spec_id)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@require_admin_context
|
||||
@ -4049,7 +4083,9 @@ def driver_initiator_data_get(context, initiator, namespace):
|
||||
PAGINATION_HELPERS = {
|
||||
models.Volume: (_volume_get_query, _process_volume_filters, _volume_get),
|
||||
models.Snapshot: (_snaps_get_query, _process_snaps_filters, _snapshot_get),
|
||||
models.Backup: (_backups_get_query, _process_backups_filters, _backup_get)
|
||||
models.Backup: (_backups_get_query, _process_backups_filters, _backup_get),
|
||||
models.QualityOfServiceSpecs: (_qos_specs_get_query,
|
||||
_process_qos_specs_filters, _qos_specs_get)
|
||||
}
|
||||
|
||||
|
||||
|
@ -22,6 +22,8 @@ import webob
|
||||
|
||||
from cinder.api.contrib import qos_specs_manage
|
||||
from cinder.api import xmlutil
|
||||
from cinder import context
|
||||
from cinder import db
|
||||
from cinder import exception
|
||||
from cinder import test
|
||||
from cinder.tests.unit.api import fakes
|
||||
@ -48,7 +50,8 @@ def stub_qos_associates(id):
|
||||
'id': 'FakeVolTypeID'}]
|
||||
|
||||
|
||||
def return_qos_specs_get_all(context):
|
||||
def return_qos_specs_get_all(context, filters=None, marker=None, limit=None,
|
||||
offset=None, sort_keys=None, sort_dirs=None):
|
||||
return [
|
||||
stub_qos_specs(1),
|
||||
stub_qos_specs(2),
|
||||
@ -142,10 +145,30 @@ def return_disassociate_all(context, id):
|
||||
|
||||
|
||||
class QoSSpecManageApiTest(test.TestCase):
|
||||
|
||||
def _create_qos_specs(self, name, values=None):
|
||||
"""Create a transfer object."""
|
||||
if values:
|
||||
specs = dict(name=name, qos_specs=values)
|
||||
else:
|
||||
specs = {'name': name,
|
||||
'qos_specs': {
|
||||
'consumer': 'back-end',
|
||||
'key1': 'value1',
|
||||
'key2': 'value2'}}
|
||||
return db.qos_specs_create(self.ctxt, specs)['id']
|
||||
|
||||
def setUp(self):
|
||||
super(QoSSpecManageApiTest, self).setUp()
|
||||
self.flags(host='fake')
|
||||
self.controller = qos_specs_manage.QoSSpecsController()
|
||||
self.ctxt = context.RequestContext(user_id='user_id',
|
||||
project_id='project_id',
|
||||
is_admin=True)
|
||||
self.qos_id1 = self._create_qos_specs("Qos_test_1")
|
||||
self.qos_id2 = self._create_qos_specs("Qos_test_2")
|
||||
self.qos_id3 = self._create_qos_specs("Qos_test_3")
|
||||
self.qos_id4 = self._create_qos_specs("Qos_test_4")
|
||||
|
||||
@mock.patch('cinder.volume.qos_specs.get_all_specs',
|
||||
side_effect=return_qos_specs_get_all)
|
||||
@ -184,6 +207,78 @@ class QoSSpecManageApiTest(test.TestCase):
|
||||
expected_names = ['qos_specs_1', 'qos_specs_2', 'qos_specs_3']
|
||||
self.assertEqual(set(expected_names), names)
|
||||
|
||||
def test_index_with_limit(self):
|
||||
url = '/v2/fake/qos-specs?limit=2'
|
||||
req = fakes.HTTPRequest.blank(url, use_admin_context=True)
|
||||
res = self.controller.index(req)
|
||||
|
||||
self.assertEqual(2, len(res['qos_specs']))
|
||||
self.assertEqual(self.qos_id4, res['qos_specs'][0]['id'])
|
||||
self.assertEqual(self.qos_id3, res['qos_specs'][1]['id'])
|
||||
|
||||
expect_next_link = ('http://localhost/v2/fakeproject/qos-specs?limit'
|
||||
'=2&marker=%s') % res['qos_specs'][1]['id']
|
||||
self.assertEqual(expect_next_link, res['qos_specs_links'][0]['href'])
|
||||
|
||||
def test_index_with_offset(self):
|
||||
url = '/v2/fake/qos-specs?offset=1'
|
||||
req = fakes.HTTPRequest.blank(url, use_admin_context=True)
|
||||
res = self.controller.index(req)
|
||||
|
||||
self.assertEqual(3, len(res['qos_specs']))
|
||||
|
||||
def test_index_with_limit_and_offset(self):
|
||||
url = '/v2/fake/qos-specs?limit=2&offset=1'
|
||||
req = fakes.HTTPRequest.blank(url, use_admin_context=True)
|
||||
res = self.controller.index(req)
|
||||
|
||||
self.assertEqual(2, len(res['qos_specs']))
|
||||
self.assertEqual(self.qos_id3, res['qos_specs'][0]['id'])
|
||||
self.assertEqual(self.qos_id2, res['qos_specs'][1]['id'])
|
||||
|
||||
def test_index_with_marker(self):
|
||||
url = '/v2/fake/qos-specs?marker=%s' % self.qos_id4
|
||||
req = fakes.HTTPRequest.blank(url, use_admin_context=True)
|
||||
res = self.controller.index(req)
|
||||
|
||||
self.assertEqual(3, len(res['qos_specs']))
|
||||
|
||||
def test_index_with_filter(self):
|
||||
url = '/v2/fake/qos-specs?id=%s' % self.qos_id4
|
||||
req = fakes.HTTPRequest.blank(url, use_admin_context=True)
|
||||
res = self.controller.index(req)
|
||||
|
||||
self.assertEqual(1, len(res['qos_specs']))
|
||||
self.assertEqual(self.qos_id4, res['qos_specs'][0]['id'])
|
||||
|
||||
def test_index_with_sort_keys(self):
|
||||
url = '/v2/fake/qos-specs?sort=id'
|
||||
req = fakes.HTTPRequest.blank(url, use_admin_context=True)
|
||||
res = self.controller.index(req)
|
||||
self.assertEqual(4, len(res['qos_specs']))
|
||||
expect_result = [self.qos_id1, self.qos_id2,
|
||||
self.qos_id3, self.qos_id4]
|
||||
expect_result.sort(reverse=True)
|
||||
|
||||
self.assertEqual(expect_result[0], res['qos_specs'][0]['id'])
|
||||
self.assertEqual(expect_result[1], res['qos_specs'][1]['id'])
|
||||
self.assertEqual(expect_result[2], res['qos_specs'][2]['id'])
|
||||
self.assertEqual(expect_result[3], res['qos_specs'][3]['id'])
|
||||
|
||||
def test_index_with_sort_keys_and_sort_dirs(self):
|
||||
url = '/v2/fake/qos-specs?sort=id:asc'
|
||||
req = fakes.HTTPRequest.blank(url, use_admin_context=True)
|
||||
res = self.controller.index(req)
|
||||
self.assertEqual(4, len(res['qos_specs']))
|
||||
expect_result = [self.qos_id1, self.qos_id2,
|
||||
self.qos_id3, self.qos_id4]
|
||||
expect_result.sort()
|
||||
|
||||
self.assertEqual(expect_result[0], res['qos_specs'][0]['id'])
|
||||
self.assertEqual(expect_result[1], res['qos_specs'][1]['id'])
|
||||
self.assertEqual(expect_result[2], res['qos_specs'][2]['id'])
|
||||
self.assertEqual(expect_result[3], res['qos_specs'][3]['id'])
|
||||
|
||||
@mock.patch('cinder.volume.qos_specs.get_qos_specs',
|
||||
side_effect=return_qos_specs_get_qos_specs)
|
||||
@mock.patch('cinder.volume.qos_specs.delete',
|
||||
|
@ -229,42 +229,12 @@ def disassociate_all(context, specs_id):
|
||||
type_id=None)
|
||||
|
||||
|
||||
def get_all_specs(context, inactive=False, search_opts=None):
|
||||
"""Get all non-deleted qos specs.
|
||||
|
||||
Pass inactive=True as argument and deleted volume types would return
|
||||
as well.
|
||||
"""
|
||||
search_opts = search_opts or {}
|
||||
qos_specs = db.qos_specs_get_all(context, inactive)
|
||||
|
||||
if search_opts:
|
||||
LOG.debug("Searching by: %s", search_opts)
|
||||
|
||||
def _check_specs_match(qos_specs, searchdict):
|
||||
for k, v in searchdict.items():
|
||||
if ((k not in qos_specs['specs'].keys() or
|
||||
qos_specs['specs'][k] != v)):
|
||||
return False
|
||||
return True
|
||||
|
||||
# search_option to filter_name mapping.
|
||||
filter_mapping = {'qos_specs': _check_specs_match}
|
||||
|
||||
result = {}
|
||||
for name, args in qos_specs.items():
|
||||
# go over all filters in the list
|
||||
for opt, values in search_opts.items():
|
||||
try:
|
||||
filter_func = filter_mapping[opt]
|
||||
except KeyError:
|
||||
# no such filter - ignore it, go to next filter
|
||||
continue
|
||||
else:
|
||||
if filter_func(args, values):
|
||||
result[name] = args
|
||||
break
|
||||
qos_specs = result
|
||||
def get_all_specs(context, filters=None, marker=None, limit=None, offset=None,
|
||||
sort_keys=None, sort_dirs=None):
|
||||
"""Get all non-deleted qos specs."""
|
||||
qos_specs = db.qos_specs_get_all(context, filters=filters, marker=marker,
|
||||
limit=limit, offset=offset,
|
||||
sort_keys=sort_keys, sort_dirs=sort_dirs)
|
||||
return qos_specs
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user