From 5dad61b1f39860c917a6d84c096fbbabed5bd4b9 Mon Sep 17 00:00:00 2001 From: Mark Doffman Date: Tue, 22 Mar 2016 10:13:15 -0500 Subject: [PATCH] Make Aggregate.get operation favor the API db This makes the Aggregate object use the API database first only falling back to the cell database if it is not available. blueprint cells-aggregate-api-db Change-Id: Id3616cef1937744bf600763bae5de50f1d27b783 --- nova/objects/aggregate.py | 34 ++++++++- nova/tests/unit/objects/test_aggregate.py | 88 +++++++++++++++++++++-- 2 files changed, 117 insertions(+), 5 deletions(-) diff --git a/nova/objects/aggregate.py b/nova/objects/aggregate.py index 97fe9b75c..4db09b2b6 100644 --- a/nova/objects/aggregate.py +++ b/nova/objects/aggregate.py @@ -15,8 +15,12 @@ from oslo_log import log as logging from oslo_utils import uuidutils +from sqlalchemy.orm import joinedload + from nova.compute import utils as compute_utils from nova import db +from nova.db.sqlalchemy import api as db_api +from nova.db.sqlalchemy import api_models from nova import exception from nova import objects from nova.objects import base @@ -24,6 +28,23 @@ from nova.objects import fields LOG = logging.getLogger(__name__) +DEPRECATED_FIELDS = ['deleted', 'deleted_at'] + + +@db_api.api_context_manager.reader +def _aggregate_get_from_db(context, aggregate_id): + query = context.session.query(api_models.Aggregate).\ + options(joinedload('_hosts')).\ + options(joinedload('_metadata')) + query = query.filter(api_models.Aggregate.id == aggregate_id) + + aggregate = query.first() + + if not aggregate: + raise exception.AggregateNotFound(aggregate_id=aggregate_id) + + return aggregate + @base.NovaObjectRegistry.register class Aggregate(base.NovaPersistentObject, base.NovaObject): @@ -49,6 +70,8 @@ class Aggregate(base.NovaPersistentObject, base.NovaObject): db_key = 'metadetails' elif key == 'uuid': continue + elif key in DEPRECATED_FIELDS and key not in db_aggregate: + continue else: db_key = key setattr(aggregate, key, db_aggregate[db_key]) @@ -59,6 +82,12 @@ class Aggregate(base.NovaPersistentObject, base.NovaObject): if db_aggregate.get('uuid'): aggregate.uuid = db_aggregate['uuid'] + # NOTE: This can be removed when we remove compatibility with + # the old aggregate model. + if any(f not in db_aggregate for f in DEPRECATED_FIELDS): + aggregate.deleted_at = None + aggregate.deleted = False + aggregate._context = context aggregate.obj_reset_changes() @@ -82,7 +111,10 @@ class Aggregate(base.NovaPersistentObject, base.NovaObject): @base.remotable_classmethod def get_by_id(cls, context, aggregate_id): - db_aggregate = db.aggregate_get(context, aggregate_id) + try: + db_aggregate = _aggregate_get_from_db(context, aggregate_id) + except exception.AggregateNotFound: + db_aggregate = db.aggregate_get(context, aggregate_id) return cls._from_db_object(context, cls(), db_aggregate) @base.remotable diff --git a/nova/tests/unit/objects/test_aggregate.py b/nova/tests/unit/objects/test_aggregate.py index 70c0c3fa2..7a39176bd 100644 --- a/nova/tests/unit/objects/test_aggregate.py +++ b/nova/tests/unit/objects/test_aggregate.py @@ -16,6 +16,8 @@ import mock from oslo_utils import timeutils from nova import db +from nova.db.sqlalchemy import api as db_api +from nova.db.sqlalchemy import api_models from nova import exception from nova.objects import aggregate from nova.tests.unit import fake_notifier @@ -36,17 +38,95 @@ fake_aggregate = { 'metadetails': {'this': 'that'}, } +fake_api_aggregate = { + 'created_at': NOW, + 'updated_at': None, + 'id': 123, + 'uuid': uuidsentinel.fake_aggregate, + 'name': 'fake-aggregate', + 'hosts': ['foo', 'bar'], + 'metadetails': {'this': 'that'}, + } + SUBS = {'metadata': 'metadetails'} +fake_db_aggregate_values = {'name': 'fake_aggregate'} + +fake_db_aggregate_metadata = {'fake_key1': 'fake_value1', + 'fake_key2': 'fake_value2', + 'availability_zone': 'fake_avail_zone'} + +fake_db_aggregate_hosts = ['foo.openstack.org'] + + +@db_api.api_context_manager.writer +def _create_aggregate(context, values=fake_db_aggregate_values, + metadata=fake_db_aggregate_metadata): + aggregate = api_models.Aggregate() + aggregate.update(values) + aggregate.save(context.session) + + for key, value in metadata.items(): + aggregate_metadata = api_models.AggregateMetadata() + aggregate_metadata.update({'key': key, + 'value': value, + 'aggregate_id': aggregate['id']}) + aggregate_metadata.save(context.session) + + return aggregate + + +@db_api.api_context_manager.writer +def _create_aggregate_with_hosts(context, values=fake_db_aggregate_values, + metadata=fake_db_aggregate_metadata, + hosts=fake_db_aggregate_hosts): + aggregate = _create_aggregate(context, values, metadata) + for host in hosts: + host = api_models.AggregateHost() + host.update({'host': 'foo.openstack.org', + 'aggregate_id': aggregate.id}) + host.save(context.session) + + return aggregate + class _TestAggregateObject(object): - def test_get_by_id(self): - self.mox.StubOutWithMock(db, 'aggregate_get') - db.aggregate_get(self.context, 123).AndReturn(fake_aggregate) - self.mox.ReplayAll() + def test_aggregate_get_from_db(self): + result = _create_aggregate_with_hosts(self.context) + expected = aggregate._aggregate_get_from_db(self.context, result['id']) + self.assertEqual(fake_db_aggregate_hosts, expected['hosts']) + self.assertEqual(fake_db_aggregate_metadata, expected['metadetails']) + + def test_aggregate_get_from_db_raise_not_found(self): + aggregate_id = 5 + self.assertRaises(exception.AggregateNotFound, + aggregate._aggregate_get_from_db, + self.context, aggregate_id) + + @mock.patch('nova.objects.aggregate._aggregate_get_from_db') + @mock.patch('nova.db.aggregate_get') + def test_get_by_id_from_api(self, mock_get, mock_get_api): + mock_get_api.return_value = fake_api_aggregate + agg = aggregate.Aggregate.get_by_id(self.context, 123) self.compare_obj(agg, fake_aggregate, subs=SUBS) + mock_get_api.assert_called_once_with(self.context, 123) + mock_get.assert_not_called() + + @mock.patch('nova.objects.aggregate._aggregate_get_from_db') + @mock.patch('nova.db.aggregate_get') + def test_get_by_id(self, mock_get, mock_get_api): + mock_get_api.side_effect = exception.AggregateNotFound( + aggregate_id=123) + mock_get.return_value = fake_aggregate + + agg = aggregate.Aggregate.get_by_id(self.context, 123) + self.compare_obj(agg, fake_aggregate, subs=SUBS) + + mock_get_api.assert_called_once_with(self.context, 123) + mock_get.assert_called_once_with(self.context, 123) + @mock.patch('nova.objects.Aggregate.save') @mock.patch('nova.db.aggregate_get') def test_load_allocates_uuid(self, mock_get, mock_save):