From 4f75ba6729366f87624554e7732ef122c6ebac4b Mon Sep 17 00:00:00 2001 From: Sergey Nikitin Date: Mon, 16 Nov 2015 12:39:36 +0300 Subject: [PATCH] Changed filter_by() to filter() during filtering instances in db API When we use filter_by() fields for filtering are extracted from the primary entity of the query, or the last entity that was the target of a call to Query.join(). If db query contains filters 'project_id' and 'tags' (or 'tags-any') db error will be raised: Entity '' has no property 'project_id' It happens because we use join(models.Tag) to filter instances by tags. Sqlalchemy try to find 'project_id' field in Tag model. To fix this issue we should use filter() instead of filter_by(). filter() works fine with join(). Closes-Bug: #1516546 Change-Id: Ibcac2a08aa51f698c690399f65c77130ff2de173 --- nova/db/sqlalchemy/api.py | 4 ++-- nova/tests/unit/db/test_db_api.py | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index fd93675ee99f..5b375a0a18ad 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2252,8 +2252,8 @@ def _exact_instance_filter(query, filters, legal_keys): # Apply simple exact matches if filter_dict: - query = query.filter_by(**filter_dict) - + query = query.filter(*[getattr(models.Instance, k) == v + for k, v in filter_dict.items()]) return query diff --git a/nova/tests/unit/db/test_db_api.py b/nova/tests/unit/db/test_db_api.py index ecdf1355ade8..78b6818c63f6 100644 --- a/nova/tests/unit/db/test_db_api.py +++ b/nova/tests/unit/db/test_db_api.py @@ -2589,6 +2589,33 @@ class InstanceTestCase(test.TestCase, ModelsObjectComparatorMixin): ignored_keys=['deleted', 'deleted_at', 'metadata', 'extra', 'system_metadata', 'info_cache', 'pci_devices']) + def test_instance_get_all_by_filters_tags_and_project_id(self): + context1 = context.RequestContext('user1', 'p1') + context2 = context.RequestContext('user2', 'p2') + + inst1 = self.create_instance_with_args(context=context1, + project_id='p1') + inst2 = self.create_instance_with_args(context=context1, + project_id='p1') + inst3 = self.create_instance_with_args(context=context2, + project_id='p2') + t1 = u'tag1' + t2 = u'tag2' + t3 = u'tag3' + t4 = u'tag4' + + db.instance_tag_set(context1, inst1.uuid, [t1, t2]) + db.instance_tag_set(context1, inst2.uuid, [t1, t2, t4]) + db.instance_tag_set(context2, inst3.uuid, [t1, t2, t3, t4]) + + result = db.instance_get_all_by_filters(self.ctxt, + {'tags': [t1, t2], + 'tags-any': [t3, t4], + 'project_id': 'p1'}) + self._assertEqualListsOfObjects([inst2], result, + ignored_keys=['deleted', 'deleted_at', 'metadata', 'extra', + 'system_metadata', 'info_cache', 'pci_devices']) + def test_instance_get_all_by_host_and_node_no_join(self): instance = self.create_instance_with_args() result = db.instance_get_all_by_host_and_node(self.ctxt, 'h1', 'n1')