Add exact filter support to SQL API list stacks
Uses a dict of KEY:VALUE to compare values of VALUE against database attribute KEY and return matching stacks. If VALUE is a list, tuple, set, or frozenset, this will evaluate to an IN clause instead. blueprint: filter-stacks Change-Id: If68edd9eaad3fe2ec4d11831d9eda9798a490e89
This commit is contained in:
parent
c4e3044c80
commit
36c4a30df7
|
@ -120,9 +120,9 @@ def stack_get_all_by_owner_id(context, owner_id):
|
||||||
|
|
||||||
|
|
||||||
def stack_get_all_by_tenant(context, limit=None, sort_keys=None,
|
def stack_get_all_by_tenant(context, limit=None, sort_keys=None,
|
||||||
marker=None, sort_dir=None):
|
marker=None, sort_dir=None, filters=None):
|
||||||
return IMPL.stack_get_all_by_tenant(context, limit, sort_keys,
|
return IMPL.stack_get_all_by_tenant(context, limit, sort_keys,
|
||||||
marker, sort_dir)
|
marker, sort_dir, filters)
|
||||||
|
|
||||||
|
|
||||||
def stack_count_all_by_tenant(context):
|
def stack_count_all_by_tenant(context):
|
||||||
|
|
|
@ -28,6 +28,7 @@ from heat.openstack.common.gettextutils import _
|
||||||
|
|
||||||
from heat.common import crypt
|
from heat.common import crypt
|
||||||
from heat.common import exception
|
from heat.common import exception
|
||||||
|
from heat.db.sqlalchemy import filters as db_filters
|
||||||
from heat.db.sqlalchemy import migration
|
from heat.db.sqlalchemy import migration
|
||||||
from heat.db.sqlalchemy import models
|
from heat.db.sqlalchemy import models
|
||||||
from heat.openstack.common.db.sqlalchemy import session as db_session
|
from heat.openstack.common.db.sqlalchemy import session as db_session
|
||||||
|
@ -284,8 +285,12 @@ def _query_stack_get_all_by_tenant(context):
|
||||||
|
|
||||||
|
|
||||||
def stack_get_all_by_tenant(context, limit=None, sort_keys=None, marker=None,
|
def stack_get_all_by_tenant(context, limit=None, sort_keys=None, marker=None,
|
||||||
sort_dir=None):
|
sort_dir=None, filters=None):
|
||||||
|
if filters is None:
|
||||||
|
filters = {}
|
||||||
|
|
||||||
query = _query_stack_get_all_by_tenant(context)
|
query = _query_stack_get_all_by_tenant(context)
|
||||||
|
query = db_filters.exact_filter(query, models.Stack, filters)
|
||||||
return _paginate_query(context, query, models.Stack, limit, sort_keys,
|
return _paginate_query(context, query, models.Stack, limit, sort_keys,
|
||||||
marker, sort_dir).all()
|
marker, sort_dir).all()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
def exact_filter(query, model, filters):
|
||||||
|
"""Applies exact match filtering to a query.
|
||||||
|
|
||||||
|
Returns the updated query. Modifies filters argument to remove
|
||||||
|
filters consumed.
|
||||||
|
|
||||||
|
:param query: query to apply filters to
|
||||||
|
:param model: model object the query applies to, for IN-style
|
||||||
|
filtering
|
||||||
|
:param filters: dictionary of filters; values that are lists,
|
||||||
|
tuples, sets, or frozensets cause an 'IN' test to
|
||||||
|
be performed, while exact matching ('==' operator)
|
||||||
|
is used for other values
|
||||||
|
"""
|
||||||
|
|
||||||
|
filter_dict = {}
|
||||||
|
|
||||||
|
for key, value in filters.iteritems():
|
||||||
|
if isinstance(value, (list, tuple, set, frozenset)):
|
||||||
|
column_attr = getattr(model, key)
|
||||||
|
query = query.filter(column_attr.in_(value))
|
||||||
|
else:
|
||||||
|
filter_dict[key] = value
|
||||||
|
|
||||||
|
if filter_dict:
|
||||||
|
query = query.filter_by(**filter_dict)
|
||||||
|
|
||||||
|
return query
|
|
@ -217,6 +217,41 @@ class SqlAlchemyTest(HeatTestCase):
|
||||||
st_db = db_api.stack_get_all_by_tenant(self.ctx)
|
st_db = db_api.stack_get_all_by_tenant(self.ctx)
|
||||||
self.assertEqual(1, len(st_db))
|
self.assertEqual(1, len(st_db))
|
||||||
|
|
||||||
|
def test_stack_get_all_by_tenant_and_filters(self):
|
||||||
|
stack1 = self._setup_test_stack('foo', UUIDs[0])
|
||||||
|
stack2 = self._setup_test_stack('bar', UUIDs[1])
|
||||||
|
stacks = [stack1, stack2]
|
||||||
|
|
||||||
|
filters = {'name': 'foo'}
|
||||||
|
results = db_api.stack_get_all_by_tenant(self.ctx,
|
||||||
|
filters=filters)
|
||||||
|
|
||||||
|
self.assertEqual(1, len(results))
|
||||||
|
self.assertEqual('foo', results[0]['name'])
|
||||||
|
|
||||||
|
def test_stack_get_all_by_tenant_filter_matches_in_list(self):
|
||||||
|
stack1 = self._setup_test_stack('foo', UUIDs[0])
|
||||||
|
stack2 = self._setup_test_stack('bar', UUIDs[1])
|
||||||
|
stacks = [stack1, stack2]
|
||||||
|
|
||||||
|
filters = {'name': ['bar', 'quux']}
|
||||||
|
results = db_api.stack_get_all_by_tenant(self.ctx,
|
||||||
|
filters=filters)
|
||||||
|
|
||||||
|
self.assertEqual(1, len(results))
|
||||||
|
self.assertEqual('bar', results[0]['name'])
|
||||||
|
|
||||||
|
def test_stack_get_all_by_tenant_returns_all_if_no_filters(self):
|
||||||
|
stack1 = self._setup_test_stack('foo', UUIDs[0])
|
||||||
|
stack2 = self._setup_test_stack('bar', UUIDs[1])
|
||||||
|
stacks = [stack1, stack2]
|
||||||
|
|
||||||
|
filters = None
|
||||||
|
results = db_api.stack_get_all_by_tenant(self.ctx,
|
||||||
|
filters=filters)
|
||||||
|
|
||||||
|
self.assertEqual(2, len(results))
|
||||||
|
|
||||||
def test_stack_get_all_by_tenant_default_sort_keys_and_dir(self):
|
def test_stack_get_all_by_tenant_default_sort_keys_and_dir(self):
|
||||||
stacks = [self._setup_test_stack('stack', x)[1] for x in UUIDs]
|
stacks = [self._setup_test_stack('stack', x)[1] for x in UUIDs]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from heat.db.sqlalchemy import filters as db_filters
|
||||||
|
from heat.tests.common import HeatTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class ExactFilterTest(HeatTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(ExactFilterTest, self).setUp()
|
||||||
|
self.query = mock.Mock()
|
||||||
|
self.model = mock.Mock()
|
||||||
|
|
||||||
|
def test_returns_same_query_for_empty_filters(self):
|
||||||
|
filters = {}
|
||||||
|
db_filters.exact_filter(self.query, self.model, filters)
|
||||||
|
self.assertEqual(0, self.query.call_count)
|
||||||
|
|
||||||
|
def test_add_exact_match_clause_for_single_values(self):
|
||||||
|
filters = {'cat': 'foo'}
|
||||||
|
db_filters.exact_filter(self.query, self.model, filters)
|
||||||
|
|
||||||
|
self.query.filter_by.assert_called_once_with(cat='foo')
|
||||||
|
|
||||||
|
def test_adds_an_in_clause_for_multiple_values(self):
|
||||||
|
self.model.cat.in_.return_value = 'fake in clause'
|
||||||
|
filters = {'cat': ['foo', 'quux']}
|
||||||
|
db_filters.exact_filter(self.query, self.model, filters)
|
||||||
|
|
||||||
|
self.query.filter.assert_called_once_with('fake in clause')
|
||||||
|
self.model.cat.in_.assert_called_once_with(['foo', 'quux'])
|
Loading…
Reference in New Issue