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:
wangxiyuan 2015-12-14 16:27:48 +08:00
parent ae2ec9bf69
commit 31fb64d694
6 changed files with 178 additions and 55 deletions

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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)
}

View File

@ -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',

View File

@ -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