diff --git a/nova/objects/aggregate.py b/nova/objects/aggregate.py index 2a2020d14f24..dfe3f890e068 100644 --- a/nova/objects/aggregate.py +++ b/nova/objects/aggregate.py @@ -417,10 +417,14 @@ def _get_by_host_from_db(context, host, key=None): @db_api.api_context_manager.reader -def _get_by_metadata_key_from_db(context, key): +def _get_by_metadata_from_db(context, key=None, value=None): + assert(key is not None or value is not None) query = context.session.query(api_models.Aggregate) query = query.join("_metadata") - query = query.filter(api_models.AggregateMetadata.key == key) + if key is not None: + query = query.filter(api_models.AggregateMetadata.key == key) + if value is not None: + query = query.filter(api_models.AggregateMetadata.value == value) query = query.options(contains_eager("_metadata")) query = query.options(joinedload("_hosts")) @@ -433,7 +437,8 @@ class AggregateList(base.ObjectListBase, base.NovaObject): # Version 1.1: Added key argument to get_by_host() # Aggregate <= version 1.1 # Version 1.2: Added get_by_metadata_key - VERSION = '1.2' + # Version 1.3: Added get_by_metadata + VERSION = '1.3' fields = { 'objects': fields.ListOfObjectsField('Aggregate'), @@ -465,8 +470,20 @@ class AggregateList(base.ObjectListBase, base.NovaObject): @base.remotable_classmethod def get_by_metadata_key(cls, context, key, hosts=None): - db_aggregates = _get_by_metadata_key_from_db(context, key=key) + db_aggregates = _get_by_metadata_from_db(context, key=key) if hosts is not None: db_aggregates = cls._filter_db_aggregates(db_aggregates, hosts) return base.obj_make_list(context, cls(context), objects.Aggregate, db_aggregates) + + @base.remotable_classmethod + def get_by_metadata(cls, context, key=None, value=None): + """Return aggregates with a metadata key set to value. + + This returns a list of all aggregates that have a metadata key + set to some value. If key is specified, then only values for + that key will qualify. + """ + db_aggregates = _get_by_metadata_from_db(context, key=key, value=value) + return base.obj_make_list(context, cls(context), objects.Aggregate, + db_aggregates) diff --git a/nova/tests/functional/db/test_aggregate.py b/nova/tests/functional/db/test_aggregate.py index df2da25ce74e..f8709fd6e45a 100644 --- a/nova/tests/functional/db/test_aggregate.py +++ b/nova/tests/functional/db/test_aggregate.py @@ -181,7 +181,7 @@ class AggregateObjectDbTestCase(test.TestCase): _create_aggregate(self.context, values={'name': 'aggregate_3'}, metadata={'badkey': 'good'}) - rl1 = aggregate_obj._get_by_metadata_key_from_db(self.context, + rl1 = aggregate_obj._get_by_metadata_from_db(self.context, key='goodkey') self.assertEqual(2, len(rl1)) @@ -204,8 +204,8 @@ class AggregateObjectDbTestCase(test.TestCase): metadata={'goodkey': 'good'}) result = aggregate_obj._aggregate_get_from_db(self.context, agg.id) - md = aggregate_obj._get_by_metadata_key_from_db(self.context, - key='goodkey') + md = aggregate_obj._get_by_metadata_from_db(self.context, + key='goodkey') self.assertEqual(len(md), 1) self.assertEqual(md[0]['id'], agg.id) self.assertEqual(result.name, fake_create_aggregate['name']) @@ -538,3 +538,26 @@ class AggregateObjectTestCase(test.TestCase): result = aggregate_obj.Aggregate.get_by_id(self.context, i) compare_obj(self, agg, fake_agg) compare_obj(self, result, fake_agg) + + def test_get_by_metadata(self): + agg = aggregate_obj.Aggregate.get_by_id(self.context, 1) + agg.update_metadata({'foo': 'bar'}) + + agg = aggregate_obj.Aggregate.get_by_id(self.context, 2) + agg.update_metadata({'foo': 'baz', + 'fu': 'bar'}) + + aggs = aggregate_obj.AggregateList.get_by_metadata( + self.context, key='foo', value='bar') + self.assertEqual(1, len(aggs)) + self.assertEqual(1, aggs[0].id) + + aggs = aggregate_obj.AggregateList.get_by_metadata( + self.context, value='bar') + self.assertEqual(2, len(aggs)) + self.assertEqual(set([1, 2]), set([a.id for a in aggs])) + + def test_get_by_metadata_from_db_assertion(self): + self.assertRaises(AssertionError, + aggregate_obj._get_by_metadata_from_db, + self.context) diff --git a/nova/tests/unit/objects/test_aggregate.py b/nova/tests/unit/objects/test_aggregate.py index d03c5b862f37..cf78d22b7570 100644 --- a/nova/tests/unit/objects/test_aggregate.py +++ b/nova/tests/unit/objects/test_aggregate.py @@ -192,7 +192,7 @@ class _TestAggregateObject(object): self.assertEqual(1, len(aggs)) self.compare_obj(aggs[0], fake_aggregate, subs=SUBS) - @mock.patch('nova.objects.aggregate._get_by_metadata_key_from_db') + @mock.patch('nova.objects.aggregate._get_by_metadata_from_db') def test_get_by_metadata_key(self, mock_api_get_by_metadata_key): mock_api_get_by_metadata_key.return_value = [fake_aggregate] aggs = aggregate.AggregateList.get_by_metadata_key( @@ -200,14 +200,14 @@ class _TestAggregateObject(object): self.assertEqual(1, len(aggs)) self.compare_obj(aggs[0], fake_aggregate, subs=SUBS) - @mock.patch('nova.objects.aggregate._get_by_metadata_key_from_db') + @mock.patch('nova.objects.aggregate._get_by_metadata_from_db') def test_get_by_metadata_key_and_hosts_no_match(self, get_by_metadata_key): get_by_metadata_key.return_value = [fake_aggregate] aggs = aggregate.AggregateList.get_by_metadata_key( self.context, 'this', hosts=['baz']) self.assertEqual(0, len(aggs)) - @mock.patch('nova.objects.aggregate._get_by_metadata_key_from_db') + @mock.patch('nova.objects.aggregate._get_by_metadata_from_db') def test_get_by_metadata_key_and_hosts_match(self, get_by_metadata_key): get_by_metadata_key.return_value = [fake_aggregate] aggs = aggregate.AggregateList.get_by_metadata_key( diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index 60bcc0e0326a..26df7f3f8788 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -1057,7 +1057,7 @@ object_data = { 'Agent': '1.0-c0c092abaceb6f51efe5d82175f15eba', 'AgentList': '1.0-5a7380d02c3aaf2a32fc8115ae7ca98c', 'Aggregate': '1.3-f315cb68906307ca2d1cca84d4753585', - 'AggregateList': '1.2-fb6e19f3c3a3186b04eceb98b5dadbfa', + 'AggregateList': '1.3-3ea55a050354e72ef3306adefa553957', 'BandwidthUsage': '1.2-c6e4c779c7f40f2407e3d70022e3cd1c', 'BandwidthUsageList': '1.2-5fe7475ada6fe62413cbfcc06ec70746', 'BlockDeviceMapping': '1.19-407e75274f48e60a76e56283333c9dbc',