Added db API layer to add instance tag-list filtering support

Added database API layer which allow filtering intances using
new filters 'not-tags' and 'not-tags-any':

 * not-tags means NOT(T1 AND T2)
 * not-tags-any means NOT(T1 OR T2)

Implements: blueprint tag-instances

Co-Authored-By: Pavel Kholkin <pkholkin@mirantis.com>

Change-Id: I0f5f8898639ecfb7a736f4d6ffcb3ccd7f03a213
This commit is contained in:
Sergey Nikitin
2015-11-23 19:35:42 +03:00
parent c729cb5302
commit 0a14f0b296
2 changed files with 173 additions and 3 deletions

View File

@@ -2247,16 +2247,24 @@ def instance_get_all_by_filters_sort(context, filters, limit=None, marker=None,
of these tags:
`tags` -- One or more strings that will be used to filter results
in an AND expression.
in an AND expression: T1 AND T2
`tags-any` -- One or more strings that will be used to filter results in
an OR expression.
an OR expression: T1 OR T2
`not-tags` -- One or more strings that will be used to filter results in
an NOT AND expression: NOT (T1 AND T2)
`not-tags-any` -- One or more strings that will be used to filter results
in an NOT OR expression: NOT (T1 OR T2)
Tags should be represented as list::
| filters = {
| 'tags': [some-tag, some-another-tag],
| 'tags-any: [some-any-tag, some-another-any-tag]
| 'tags-any: [some-any-tag, some-another-any-tag],
| 'not-tags: [some-not-tag, some-another-not-tag],
| 'not-tags-any: [some-not-any-tag, some-another-not-any-tag]
| }
"""
@@ -2349,6 +2357,25 @@ def instance_get_all_by_filters_sort(context, filters, limit=None, marker=None,
query_prefix = query_prefix.join(tag_alias, models.Instance.tags)
query_prefix = query_prefix.filter(tag_alias.tag.in_(tags))
if 'not-tags' in filters:
tags = filters.pop('not-tags')
first_tag = tags.pop(0)
subq = query_prefix.session.query(models.Tag.resource_id)
subq = subq.join(models.Instance.tags)
subq = subq.filter(models.Tag.tag == first_tag)
for tag in tags:
tag_alias = aliased(models.Tag)
subq = subq.join(tag_alias, models.Instance.tags)
subq = subq.filter(tag_alias.tag == tag)
query_prefix = query_prefix.filter(~models.Instance.uuid.in_(subq))
if 'not-tags-any' in filters:
tags = filters.pop('not-tags-any')
query_prefix = query_prefix.filter(~models.Instance.tags.any(
models.Tag.tag.in_(tags)))
if not context.is_admin:
# If we're not admin context, add appropriate filter..
if context.project_id:

View File

@@ -9848,3 +9848,146 @@ class TestInstanceInfoCache(test.TestCase):
info_cache = db.instance_info_cache_get(self.context, instance_uuid)
self.assertEqual(network_info, info_cache.network_info)
self.assertEqual(instance_uuid, info_cache.instance_uuid)
class TestInstanceTagsFiltering(test.TestCase):
sample_data = {
'project_id': 'project1'
}
def setUp(self):
super(TestInstanceTagsFiltering, self).setUp()
self.ctxt = context.RequestContext('user1', 'project1')
def _create_instances(self, count):
return [db.instance_create(self.ctxt, self.sample_data)['uuid']
for i in range(count)]
def _assertEqualInstanceUUIDs(self, expected_uuids, observed_instances):
observed_uuids = [inst['uuid'] for inst in observed_instances]
self.assertEqual(sorted(expected_uuids), sorted(observed_uuids))
def test_instance_get_all_by_filters_not_tags(self):
uuids = self._create_instances(8)
db.instance_tag_set(self.ctxt, uuids[0], [u't1'])
db.instance_tag_set(self.ctxt, uuids[1], [u't2'])
db.instance_tag_set(self.ctxt, uuids[2], [u't1', u't2'])
db.instance_tag_set(self.ctxt, uuids[3], [u't2', u't3'])
db.instance_tag_set(self.ctxt, uuids[4], [u't3'])
db.instance_tag_set(self.ctxt, uuids[5], [u't1', u't2', u't3'])
db.instance_tag_set(self.ctxt, uuids[6], [u't3', u't4'])
db.instance_tag_set(self.ctxt, uuids[7], [])
result = db.instance_get_all_by_filters(
self.ctxt, {'not-tags': [u't1', u't2']})
self._assertEqualInstanceUUIDs([uuids[0], uuids[1], uuids[3], uuids[4],
uuids[6], uuids[7]], result)
def test_instance_get_all_by_filters_not_tags_any(self):
uuids = self._create_instances(8)
db.instance_tag_set(self.ctxt, uuids[0], [u't1'])
db.instance_tag_set(self.ctxt, uuids[1], [u't2'])
db.instance_tag_set(self.ctxt, uuids[2], [u't1', u't2'])
db.instance_tag_set(self.ctxt, uuids[3], [u't2', u't3'])
db.instance_tag_set(self.ctxt, uuids[4], [u't3'])
db.instance_tag_set(self.ctxt, uuids[5], [u't1', u't2', u't3'])
db.instance_tag_set(self.ctxt, uuids[6], [u't3', u't4'])
db.instance_tag_set(self.ctxt, uuids[7], [])
result = db.instance_get_all_by_filters(
self.ctxt, {'not-tags-any': [u't1', u't2']})
self._assertEqualInstanceUUIDs([uuids[4], uuids[6], uuids[7]], result)
def test_instance_get_all_by_filters_not_tags_and_tags(self):
uuids = self._create_instances(5)
db.instance_tag_set(self.ctxt, uuids[0], [u't1', u't2', u't4', u't5'])
db.instance_tag_set(self.ctxt, uuids[1], [u't1', u't2', u't4'])
db.instance_tag_set(self.ctxt, uuids[2], [u't1', u't2', u't3'])
db.instance_tag_set(self.ctxt, uuids[3], [u't1', u't3'])
db.instance_tag_set(self.ctxt, uuids[4], [])
result = db.instance_get_all_by_filters(self.ctxt,
{'tags': [u't1', u't2'],
'not-tags': [u't4', u't5']})
self._assertEqualInstanceUUIDs([uuids[1], uuids[2]], result)
def test_instance_get_all_by_filters_tags_contradictory(self):
uuids = self._create_instances(4)
db.instance_tag_set(self.ctxt, uuids[0], [u't1'])
db.instance_tag_set(self.ctxt, uuids[1], [u't2', u't3'])
db.instance_tag_set(self.ctxt, uuids[2], [u't1', u't2'])
db.instance_tag_set(self.ctxt, uuids[3], [])
result = db.instance_get_all_by_filters(self.ctxt,
{'tags': [u't1'],
'not-tags': [u't1']})
self.assertEqual([], result)
result = db.instance_get_all_by_filters(self.ctxt,
{'tags': [u't1'],
'not-tags-any': [u't1']})
self.assertEqual([], result)
result = db.instance_get_all_by_filters(self.ctxt,
{'tags-any': [u't1'],
'not-tags-any': [u't1']})
self.assertEqual([], result)
result = db.instance_get_all_by_filters(self.ctxt,
{'tags-any': [u't1'],
'not-tags': [u't1']})
self.assertEqual([], result)
def test_instance_get_all_by_filters_not_tags_and_tags_any(self):
uuids = self._create_instances(6)
db.instance_tag_set(self.ctxt, uuids[0], [u't1'])
db.instance_tag_set(self.ctxt, uuids[1], [u't2'])
db.instance_tag_set(self.ctxt, uuids[2], [u't1', u't2'])
db.instance_tag_set(self.ctxt, uuids[3], [u't1', u't3'])
db.instance_tag_set(self.ctxt, uuids[4], [u't1', u't2', u't3'])
db.instance_tag_set(self.ctxt, uuids[5], [])
result = db.instance_get_all_by_filters(self.ctxt,
{'tags-any': [u't1', u't2'],
'not-tags': [u't1', u't2']})
self._assertEqualInstanceUUIDs([uuids[0], uuids[1], uuids[3]], result)
def test_instance_get_all_by_filters_not_tags_and_not_tags_any(self):
uuids = self._create_instances(6)
db.instance_tag_set(self.ctxt, uuids[0], [u't1'])
db.instance_tag_set(self.ctxt, uuids[1], [u't2', u't5'])
db.instance_tag_set(self.ctxt, uuids[2], [u't1', u't2'])
db.instance_tag_set(self.ctxt, uuids[3], [u't1', u't3'])
db.instance_tag_set(self.ctxt, uuids[4], [u't1', u't2', u't4', u't5'])
db.instance_tag_set(self.ctxt, uuids[5], [])
result = db.instance_get_all_by_filters(self.ctxt,
{'not-tags': [u't1', u't2'],
'not-tags-any': [u't3', u't4']})
self._assertEqualInstanceUUIDs([uuids[0], uuids[1], uuids[5]], result)
def test_instance_get_all_by_filters_all_tag_filters(self):
uuids = self._create_instances(9)
db.instance_tag_set(self.ctxt, uuids[0], [u't1', u't3', u't7'])
db.instance_tag_set(self.ctxt, uuids[1], [u't1', u't2'])
db.instance_tag_set(self.ctxt, uuids[2], [u't1', u't2', u't7'])
db.instance_tag_set(self.ctxt, uuids[3], [u't1', u't2', u't3', u't5'])
db.instance_tag_set(self.ctxt, uuids[4], [u't1', u't2', u't3', u't7'])
db.instance_tag_set(self.ctxt, uuids[5], [u't1', u't2', u't3'])
db.instance_tag_set(self.ctxt, uuids[6], [u't1', u't2', u't3', u't4',
u't5'])
db.instance_tag_set(self.ctxt, uuids[7], [u't1', u't2', u't3', u't4',
u't5', u't6'])
db.instance_tag_set(self.ctxt, uuids[8], [])
result = db.instance_get_all_by_filters(self.ctxt,
{'tags': [u't1', u't2'],
'tags-any': [u't3', u't4'],
'not-tags': [u't5', u't6'],
'not-tags-any': [u't7', u't8']})
self._assertEqualInstanceUUIDs([uuids[3], uuids[5], uuids[6]], result)