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
This commit is contained in:
Mark Doffman 2016-03-22 10:13:15 -05:00
parent 04e4e95605
commit 5dad61b1f3
2 changed files with 117 additions and 5 deletions

View File

@ -15,8 +15,12 @@
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import uuidutils from oslo_utils import uuidutils
from sqlalchemy.orm import joinedload
from nova.compute import utils as compute_utils from nova.compute import utils as compute_utils
from nova import db 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 exception
from nova import objects from nova import objects
from nova.objects import base from nova.objects import base
@ -24,6 +28,23 @@ from nova.objects import fields
LOG = logging.getLogger(__name__) 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 @base.NovaObjectRegistry.register
class Aggregate(base.NovaPersistentObject, base.NovaObject): class Aggregate(base.NovaPersistentObject, base.NovaObject):
@ -49,6 +70,8 @@ class Aggregate(base.NovaPersistentObject, base.NovaObject):
db_key = 'metadetails' db_key = 'metadetails'
elif key == 'uuid': elif key == 'uuid':
continue continue
elif key in DEPRECATED_FIELDS and key not in db_aggregate:
continue
else: else:
db_key = key db_key = key
setattr(aggregate, key, db_aggregate[db_key]) setattr(aggregate, key, db_aggregate[db_key])
@ -59,6 +82,12 @@ class Aggregate(base.NovaPersistentObject, base.NovaObject):
if db_aggregate.get('uuid'): if db_aggregate.get('uuid'):
aggregate.uuid = db_aggregate['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._context = context
aggregate.obj_reset_changes() aggregate.obj_reset_changes()
@ -82,6 +111,9 @@ class Aggregate(base.NovaPersistentObject, base.NovaObject):
@base.remotable_classmethod @base.remotable_classmethod
def get_by_id(cls, context, aggregate_id): def get_by_id(cls, context, aggregate_id):
try:
db_aggregate = _aggregate_get_from_db(context, aggregate_id)
except exception.AggregateNotFound:
db_aggregate = db.aggregate_get(context, aggregate_id) db_aggregate = db.aggregate_get(context, aggregate_id)
return cls._from_db_object(context, cls(), db_aggregate) return cls._from_db_object(context, cls(), db_aggregate)

View File

@ -16,6 +16,8 @@ import mock
from oslo_utils import timeutils from oslo_utils import timeutils
from nova import db 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 exception
from nova.objects import aggregate from nova.objects import aggregate
from nova.tests.unit import fake_notifier from nova.tests.unit import fake_notifier
@ -36,17 +38,95 @@ fake_aggregate = {
'metadetails': {'this': 'that'}, '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'} 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): class _TestAggregateObject(object):
def test_get_by_id(self): def test_aggregate_get_from_db(self):
self.mox.StubOutWithMock(db, 'aggregate_get') result = _create_aggregate_with_hosts(self.context)
db.aggregate_get(self.context, 123).AndReturn(fake_aggregate) expected = aggregate._aggregate_get_from_db(self.context, result['id'])
self.mox.ReplayAll() 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) agg = aggregate.Aggregate.get_by_id(self.context, 123)
self.compare_obj(agg, fake_aggregate, subs=SUBS) 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.objects.Aggregate.save')
@mock.patch('nova.db.aggregate_get') @mock.patch('nova.db.aggregate_get')
def test_load_allocates_uuid(self, mock_get, mock_save): def test_load_allocates_uuid(self, mock_get, mock_save):