]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add pagination support to Qos specs
authorwangxiyuan <wangxiyuan@huawei.com>
Mon, 14 Dec 2015 08:27:48 +0000 (16:27 +0800)
committerwangxiyuan <wangxiyuan@huawei.com>
Wed, 23 Dec 2015 08:26:34 +0000 (16:26 +0800)
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

cinder/api/contrib/qos_specs_manage.py
cinder/api/views/qos_specs.py
cinder/db/api.py
cinder/db/sqlalchemy/api.py
cinder/tests/unit/api/contrib/test_qos_specs_manage.py
cinder/volume/qos_specs.py

index fe632f6843dc65897bb61bfa4ba670d01cc4dd1a..d272bcb3d57d8c546703d1a3b42d09889264114f 100644 (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)
index 301623feaa7f09a175c5777d53b67b5f662176c5..9862056ca170a2c526570ec43d25085682696dd3 100644 (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
index dcd302ba89062f594ad6d3b6e41ac66a8f39e5a4..8d7d55ac9c83816b3628d4e4b233d9c0fb7650c2 100644 (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):
index 0dd40d1c210fea87eee7e05de4bbe67b9ec88cdd..3092313be3ad27de88fa0f2899f12c19bdb9c70b 100644 (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)
 }
 
 
index 749572be6c51d0d3fdf335b3798cf673da7e48a9..c22b09d6caa914db4f7d8c52aad8962dde67cbd4 100644 (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',
index efe74afe9f2a87cdb0befa54b18933547018a924..529be993faaab5413d11d31ea1c0af3f87c7c3c3 100644 (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