summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2016-07-29 00:59:46 +0000
committerGerrit Code Review <review@openstack.org>2016-07-29 00:59:46 +0000
commite75956043b54f911f202fd1e876cdfabc33647bd (patch)
treea06d0cf1b31b314d84fdffe1aeeadcf3b0753014
parent4fdf8b721af21e9afc54e2acc1f13ecd3d851f6a (diff)
parent93b44ff5a022314a92a67b145feb33a7ef9a9df9 (diff)
downloadnova-e75956043b54f911f202fd1e876cdfabc33647bd.tar.gz
Merge "Make Aggregate metadata functions work with API db"
-rw-r--r--nova/objects/aggregate.py77
-rw-r--r--nova/tests/functional/db/test_aggregate.py89
-rw-r--r--nova/tests/unit/objects/test_aggregate.py63
3 files changed, 221 insertions, 8 deletions
diff --git a/nova/objects/aggregate.py b/nova/objects/aggregate.py
index bba19e6a4e..aac2715292 100644
--- a/nova/objects/aggregate.py
+++ b/nova/objects/aggregate.py
@@ -14,6 +14,7 @@
from oslo_db import exception as db_exc
from oslo_log import log as logging
+from oslo_utils import excutils
from oslo_utils import uuidutils
from sqlalchemy.orm import contains_eager
from sqlalchemy.orm import joinedload
@@ -23,6 +24,7 @@ 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.i18n import _
from nova import objects
from nova.objects import base
from nova.objects import fields
@@ -93,6 +95,70 @@ def _host_delete_from_db(context, aggregate_id, host):
host=host)
+def _metadata_add_to_db(context, aggregate_id, metadata, max_retries=10,
+ set_delete=False):
+ all_keys = metadata.keys()
+ for attempt in range(max_retries):
+ try:
+ with db_api.api_context_manager.writer.using(context):
+ query = context.session.query(api_models.AggregateMetadata).\
+ filter_by(aggregate_id=aggregate_id)
+
+ if set_delete:
+ query.filter(~api_models.AggregateMetadata.key.
+ in_(all_keys)).\
+ delete(synchronize_session=False)
+
+ already_existing_keys = set()
+ if all_keys:
+ query = query.filter(
+ api_models.AggregateMetadata.key.in_(all_keys))
+ for meta_ref in query.all():
+ key = meta_ref.key
+ meta_ref.update({"value": metadata[key]})
+ already_existing_keys.add(key)
+
+ new_entries = []
+ for key, value in metadata.items():
+ if key in already_existing_keys:
+ continue
+ new_entries.append({"key": key,
+ "value": value,
+ "aggregate_id": aggregate_id})
+ if new_entries:
+ context.session.execute(
+ api_models.AggregateMetadata.__table__.insert(),
+ new_entries)
+
+ return metadata
+ except db_exc.DBDuplicateEntry:
+ # a concurrent transaction has been committed,
+ # try again unless this was the last attempt
+ with excutils.save_and_reraise_exception() as ctxt:
+ if attempt < max_retries - 1:
+ ctxt.reraise = False
+ else:
+ msg = _("Add metadata failed for aggregate %(id)s "
+ "after %(retries)s retries") % \
+ {"id": aggregate_id, "retries": max_retries}
+ LOG.warning(msg)
+
+
+@db_api.api_context_manager.writer
+def _metadata_delete_from_db(context, aggregate_id, key):
+ # Check to see if the aggregate exists
+ _aggregate_get_from_db(context, aggregate_id)
+
+ query = context.session.query(api_models.AggregateMetadata)
+ query = query.filter(api_models.AggregateMetadata.aggregate_id ==
+ aggregate_id)
+ count = query.filter_by(key=key).delete()
+
+ if count == 0:
+ raise exception.AggregateMetadataNotFound(
+ aggregate_id=aggregate_id, metadata_key=key)
+
+
@base.NovaObjectRegistry.register
class Aggregate(base.NovaPersistentObject, base.NovaObject):
# Version 1.0: Initial version
@@ -237,6 +303,13 @@ class Aggregate(base.NovaPersistentObject, base.NovaObject):
@base.remotable
def update_metadata(self, updates):
+ if self.in_api:
+ metadata_delete = _metadata_delete_from_db
+ metadata_add = _metadata_add_to_db
+ else:
+ metadata_delete = db.aggregate_metadata_delete
+ metadata_add = db.aggregate_metadata_add
+
payload = {'aggregate_id': self.id,
'meta_data': updates}
compute_utils.notify_about_aggregate_update(self._context,
@@ -246,7 +319,7 @@ class Aggregate(base.NovaPersistentObject, base.NovaObject):
for key, value in updates.items():
if value is None:
try:
- db.aggregate_metadata_delete(self._context, self.id, key)
+ metadata_delete(self._context, self.id, key)
except exception.AggregateMetadataNotFound:
pass
try:
@@ -256,7 +329,7 @@ class Aggregate(base.NovaPersistentObject, base.NovaObject):
else:
to_add[key] = value
self.metadata[key] = value
- db.aggregate_metadata_add(self._context, self.id, to_add)
+ metadata_add(self._context, self.id, to_add)
compute_utils.notify_about_aggregate_update(self._context,
"updatemetadata.end",
payload)
diff --git a/nova/tests/functional/db/test_aggregate.py b/nova/tests/functional/db/test_aggregate.py
index dbabbb25b8..eb52890725 100644
--- a/nova/tests/functional/db/test_aggregate.py
+++ b/nova/tests/functional/db/test_aggregate.py
@@ -10,6 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from copy import deepcopy
+import mock
+from oslo_db import exception as db_exc
from oslo_utils import timeutils
from nova import context
@@ -19,6 +22,7 @@ from nova.db.sqlalchemy import api_models
from nova import exception
from nova import test
from nova.tests import fixtures
+from nova.tests.unit import matchers
from nova.tests import uuidsentinel
import nova.objects.aggregate as aggregate_obj
@@ -95,6 +99,16 @@ def _aggregate_host_get_all(context, aggregate_id):
filter_by(aggregate_id=aggregate_id).all()
+@db_api.api_context_manager.reader
+def _aggregate_metadata_get_all(context, aggregate_id):
+ results = context.session.query(api_models.AggregateMetadata).\
+ filter_by(aggregate_id=aggregate_id).all()
+ metadata = {}
+ for r in results:
+ metadata[r['key']] = r['value']
+ return metadata
+
+
class AggregateObjectDbTestCase(test.NoDBTestCase):
USES_DB_SELF = True
@@ -251,3 +265,78 @@ class AggregateObjectDbTestCase(test.NoDBTestCase):
aggregate_obj._host_delete_from_db,
self.context, result['id'],
_get_fake_hosts(1)[0])
+
+ def test_aggregate_metadata_add(self):
+ result = _create_aggregate(self.context, metadata=None)
+ metadata = deepcopy(_get_fake_metadata(1))
+ aggregate_obj._metadata_add_to_db(self.context, result['id'], metadata)
+ expected = _aggregate_metadata_get_all(self.context, result['id'])
+ self.assertThat(metadata, matchers.DictMatches(expected))
+
+ def test_aggregate_metadata_add_empty_metadata(self):
+ result = _create_aggregate(self.context, metadata=None)
+ metadata = {}
+ aggregate_obj._metadata_add_to_db(self.context, result['id'], metadata)
+ expected = _aggregate_metadata_get_all(self.context, result['id'])
+ self.assertThat(metadata, matchers.DictMatches(expected))
+
+ def test_aggregate_metadata_add_and_update(self):
+ result = _create_aggregate(self.context)
+ metadata = deepcopy(_get_fake_metadata(1))
+ key = list(metadata.keys())[0]
+ new_metadata = {key: 'foo',
+ 'fake_new_key': 'fake_new_value'}
+ metadata.update(new_metadata)
+ aggregate_obj._metadata_add_to_db(self.context,
+ result['id'], new_metadata)
+ expected = _aggregate_metadata_get_all(self.context, result['id'])
+ self.assertThat(metadata, matchers.DictMatches(expected))
+
+ def test_aggregate_metadata_add_retry(self):
+ result = _create_aggregate(self.context, metadata=None)
+ with mock.patch('nova.db.sqlalchemy.api_models.'
+ 'AggregateMetadata.__table__.insert') as insert_mock:
+ insert_mock.side_effect = db_exc.DBDuplicateEntry
+ self.assertRaises(db_exc.DBDuplicateEntry,
+ aggregate_obj._metadata_add_to_db,
+ self.context,
+ result['id'],
+ {'fake_key2': 'fake_value2'},
+ max_retries=5)
+
+ def test_aggregate_metadata_update(self):
+ result = _create_aggregate(self.context)
+ metadata = deepcopy(_get_fake_metadata(1))
+ key = list(metadata.keys())[0]
+ aggregate_obj._metadata_delete_from_db(self.context, result['id'], key)
+ new_metadata = {key: 'foo'}
+ aggregate_obj._metadata_add_to_db(self.context,
+ result['id'], new_metadata)
+ expected = _aggregate_metadata_get_all(self.context, result['id'])
+ metadata[key] = 'foo'
+ self.assertThat(metadata, matchers.DictMatches(expected))
+
+ def test_aggregate_metadata_delete(self):
+ result = _create_aggregate(self.context, metadata=None)
+ metadata = deepcopy(_get_fake_metadata(1))
+ aggregate_obj._metadata_add_to_db(self.context, result['id'], metadata)
+ aggregate_obj._metadata_delete_from_db(self.context, result['id'],
+ list(metadata.keys())[0])
+ expected = _aggregate_metadata_get_all(self.context, result['id'])
+ del metadata[list(metadata.keys())[0]]
+ self.assertThat(metadata, matchers.DictMatches(expected))
+
+ def test_aggregate_remove_availability_zone(self):
+ result = _create_aggregate(self.context, metadata={'availability_zone':
+ 'fake_avail_zone'})
+ aggregate_obj._metadata_delete_from_db(self.context,
+ result['id'],
+ 'availability_zone')
+ aggr = aggregate_obj._aggregate_get_from_db(self.context, result['id'])
+ self.assertIsNone(aggr['availability_zone'])
+
+ def test_aggregate_metadata_delete_raise_not_found(self):
+ result = _create_aggregate(self.context)
+ self.assertRaises(exception.AggregateMetadataNotFound,
+ aggregate_obj._metadata_delete_from_db,
+ self.context, result['id'], 'foo_key')
diff --git a/nova/tests/unit/objects/test_aggregate.py b/nova/tests/unit/objects/test_aggregate.py
index 906f1fdc02..371bd6f7d5 100644
--- a/nova/tests/unit/objects/test_aggregate.py
+++ b/nova/tests/unit/objects/test_aggregate.py
@@ -162,9 +162,15 @@ class _TestAggregateObject(object):
self.assertRaises(exception.ObjectActionError,
agg.save)
- @mock.patch.object(db, 'aggregate_metadata_delete')
- @mock.patch.object(db, 'aggregate_metadata_add')
- def test_update_metadata(self, mock_add, mock_delete):
+ @mock.patch('nova.objects.aggregate._metadata_delete_from_db')
+ @mock.patch('nova.objects.aggregate._metadata_add_to_db')
+ @mock.patch('nova.db.aggregate_metadata_delete')
+ @mock.patch('nova.db.aggregate_metadata_add')
+ def test_update_metadata(self,
+ mock_metadata_add,
+ mock_metadata_delete,
+ mock_api_metadata_add,
+ mock_api_metadata_delete):
fake_notifier.NOTIFICATIONS = []
agg = aggregate.Aggregate()
agg._context = self.context
@@ -182,10 +188,55 @@ class _TestAggregateObject(object):
self.assertEqual({'todelete': None, 'toadd': 'myval'},
msg.payload['meta_data'])
self.assertEqual({'foo': 'bar', 'toadd': 'myval'}, agg.metadata)
+ mock_metadata_add.assert_called_once_with(self.context, 123,
+ {'toadd': 'myval'})
+ mock_metadata_delete.assert_called_once_with(self.context, 123,
+ 'todelete')
+ self.assertFalse(mock_api_metadata_add.called)
+ self.assertFalse(mock_api_metadata_delete.called)
- mock_delete.assert_called_once_with(self.context, 123, 'todelete')
- mock_add.assert_called_once_with(self.context, 123,
- {'toadd': 'myval'})
+ @mock.patch('nova.objects.Aggregate.in_api')
+ @mock.patch('nova.objects.aggregate._metadata_delete_from_db')
+ @mock.patch('nova.objects.aggregate._metadata_add_to_db')
+ @mock.patch('nova.db.aggregate_metadata_delete')
+ @mock.patch('nova.db.aggregate_metadata_add')
+ def test_update_metadata_api(self,
+ mock_metadata_add,
+ mock_metadata_delete,
+ mock_api_metadata_add,
+ mock_api_metadata_delete,
+ mock_in_api):
+ mock_in_api.return_value = True
+ fake_notifier.NOTIFICATIONS = []
+ agg = aggregate.Aggregate()
+ agg._context = self.context
+ agg.id = 123
+ agg.metadata = {'foo': 'bar'}
+ agg.obj_reset_changes()
+ agg.update_metadata({'todelete': None, 'toadd': 'myval'})
+ self.assertEqual(2, len(fake_notifier.NOTIFICATIONS))
+ msg = fake_notifier.NOTIFICATIONS[0]
+ self.assertEqual('aggregate.updatemetadata.start', msg.event_type)
+ self.assertEqual({'todelete': None, 'toadd': 'myval'},
+ msg.payload['meta_data'])
+ msg = fake_notifier.NOTIFICATIONS[1]
+ self.assertEqual('aggregate.updatemetadata.end', msg.event_type)
+ self.assertEqual({'todelete': None, 'toadd': 'myval'},
+ msg.payload['meta_data'])
+ self.assertEqual({'foo': 'bar', 'toadd': 'myval'}, agg.metadata)
+ mock_api_metadata_delete.assert_called_once_with(self.context, 123,
+ 'todelete')
+ mock_api_metadata_add.assert_called_once_with(self.context, 123,
+ {'toadd': 'myval'})
+ self.assertFalse(mock_metadata_add.called)
+ self.assertFalse(mock_metadata_delete.called)
+
+ mock_api_metadata_delete.assert_called_once_with(self.context,
+ 123,
+ 'todelete')
+ mock_api_metadata_add.assert_called_once_with(self.context,
+ 123,
+ {'toadd': 'myval'})
@mock.patch.object(db, 'aggregate_delete')
def test_destroy(self, mock_aggregate_delete):