From c5c4f71aa0963ddd808bbcf755bc9ac675cf2755 Mon Sep 17 00:00:00 2001 From: melanie witt Date: Fri, 17 Jun 2016 09:31:05 +0000 Subject: [PATCH] Make InstanceGroup object favor the API database This makes the InstanceGroup object load first from the API database, falling back to the main database as necessary. Creates happen in the API database only now. Part of blueprint cells-instance-groups-api-db Change-Id: I2fe7524818e92a4d9a55f84633e70091a42531ea --- nova/objects/instance_group.py | 309 ++++++++++++++++-- .../functional/db/test_instance_group.py | 185 +++++++++++ .../tests/unit/objects/test_instance_group.py | 124 ++++--- 3 files changed, 533 insertions(+), 85 deletions(-) create mode 100644 nova/tests/functional/db/test_instance_group.py diff --git a/nova/objects/instance_group.py b/nova/objects/instance_group.py index b1280df82aca..b8e2365141fe 100644 --- a/nova/objects/instance_group.py +++ b/nova/objects/instance_group.py @@ -12,11 +12,18 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + +from oslo_db import exception as db_exc from oslo_utils import uuidutils from oslo_utils import versionutils +from sqlalchemy.orm import contains_eager +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 @@ -26,6 +33,79 @@ from nova.objects import fields LAZY_LOAD_FIELDS = ['hosts'] +def _instance_group_get_query(context, id_field=None, id=None): + query = context.session.query(api_models.InstanceGroup).\ + options(joinedload('_policies')).\ + options(joinedload('_members')) + if not context.is_admin: + query = query.filter_by(project_id=context.project_id) + if id and id_field: + query = query.filter(id_field == id) + return query + + +def _instance_group_model_get_query(context, model_class, group_id): + return context.session.query(model_class).filter_by(group_id=group_id) + + +def _instance_group_model_add(context, model_class, items, item_models, field, + group_id, append_to_models=None): + models = [] + already_existing = set() + for db_item in item_models: + already_existing.add(getattr(db_item, field)) + models.append(db_item) + for item in items: + if item in already_existing: + continue + model = model_class() + values = {'group_id': group_id} + values[field] = item + model.update(values) + context.session.add(model) + if append_to_models: + append_to_models.append(model) + models.append(model) + return models + + +def _instance_group_policies_add(context, group, policies): + query = _instance_group_model_get_query(context, + api_models.InstanceGroupPolicy, + group.id) + query = query.filter( + api_models.InstanceGroupPolicy.policy.in_(set(policies))) + return _instance_group_model_add(context, api_models.InstanceGroupPolicy, + policies, query.all(), 'policy', group.id, + append_to_models=group._policies) + + +def _instance_group_members_add(context, group, members): + query = _instance_group_model_get_query(context, + api_models.InstanceGroupMember, + group.id) + query = query.filter( + api_models.InstanceGroupMember.instance_uuid.in_(set(members))) + return _instance_group_model_add(context, api_models.InstanceGroupMember, + members, query.all(), 'instance_uuid', + group.id, append_to_models=group._members) + + +def _instance_group_members_add_by_uuid(context, group_uuid, members): + # NOTE(melwitt): The condition on the join limits the number of members + # returned to only those we wish to check as already existing. + group = context.session.query(api_models.InstanceGroup).\ + outerjoin(api_models.InstanceGroupMember, + api_models.InstanceGroupMember.instance_uuid.in_(set(members))).\ + filter(api_models.InstanceGroup.uuid == group_uuid).\ + options(contains_eager('_members')).first() + if not group: + raise exception.InstanceGroupNotFound(group_uuid=group_uuid) + return _instance_group_model_add(context, api_models.InstanceGroupMember, + members, group._members, 'instance_uuid', + group.id) + + # TODO(berrange): Remove NovaObjectDictCompat @base.NovaObjectRegistry.register class InstanceGroup(base.NovaPersistentObject, base.NovaObject, @@ -74,8 +154,15 @@ class InstanceGroup(base.NovaPersistentObject, base.NovaObject, for field in instance_group.fields: if field in LAZY_LOAD_FIELDS: continue - if field == 'deleted': - instance_group.deleted = db_inst['deleted'] == db_inst['id'] + # This is needed to handle db models from both the api + # database and the main database. In the migration to + # the api database, we have removed soft-delete, so + # the object fields for delete must be filled in with + # default values for db models from the api database. + ignore = {'deleted': False, + 'deleted_at': None} + if field in ignore and not hasattr(db_inst, field): + instance_group[field] = ignore[field] else: instance_group[field] = db_inst[field] @@ -83,6 +170,114 @@ class InstanceGroup(base.NovaPersistentObject, base.NovaObject, instance_group.obj_reset_changes() return instance_group + @staticmethod + @db_api.api_context_manager.reader + def _get_from_db_by_uuid(context, uuid): + grp = _instance_group_get_query(context, + id_field=api_models.InstanceGroup.uuid, + id=uuid).first() + if not grp: + raise exception.InstanceGroupNotFound(group_uuid=uuid) + return grp + + @staticmethod + @db_api.api_context_manager.reader + def _get_from_db_by_id(context, id): + grp = _instance_group_get_query(context, + id_field=api_models.InstanceGroup.id, + id=id).first() + if not grp: + raise exception.InstanceGroupNotFound(group_uuid=id) + return grp + + @staticmethod + @db_api.api_context_manager.reader + def _get_from_db_by_name(context, name): + grp = _instance_group_get_query(context).filter_by(name=name).first() + if not grp: + raise exception.InstanceGroupNotFound(group_uuid=name) + return grp + + @staticmethod + @db_api.api_context_manager.reader + def _get_from_db_by_instance(context, instance_uuid): + grp_member = context.session.query(api_models.InstanceGroupMember).\ + filter_by(instance_uuid=instance_uuid).first() + if not grp_member: + raise exception.InstanceGroupNotFound(group_uuid='') + grp = InstanceGroup._get_from_db_by_id(context, grp_member.group_id) + return grp + + @staticmethod + @db_api.api_context_manager.writer + def _save_in_db(context, group_uuid, values): + grp = _instance_group_get_query(context, + id_field=api_models.InstanceGroup.uuid, + id=group_uuid).first() + if not grp: + raise exception.InstanceGroupNotFound(group_uuid=group_uuid) + + values_copy = copy.copy(values) + policies = values_copy.pop('policies', None) + members = values_copy.pop('members', None) + + grp.update(values_copy) + + if policies is not None: + _instance_group_policies_add(context, grp, policies) + if members is not None: + _instance_group_members_add(context, grp, members) + + return grp + + @staticmethod + @db_api.api_context_manager.writer + def _create_in_db(context, values, policies=None, members=None): + try: + group = api_models.InstanceGroup() + group.update(values) + group.save(context.session) + except db_exc.DBDuplicateEntry: + raise exception.InstanceGroupIdExists(group_uuid=values['uuid']) + + if policies: + group._policies = _instance_group_policies_add(context, group, + policies) + else: + group._policies = [] + + if members: + group._members = _instance_group_members_add(context, group, + members) + else: + group._members = [] + + return group + + @staticmethod + @db_api.api_context_manager.writer + def _destroy_in_db(context, group_uuid): + qry = _instance_group_get_query(context, + id_field=api_models.InstanceGroup.uuid, + id=group_uuid) + if qry.count() == 0: + raise exception.InstanceGroupNotFound(group_uuid=group_uuid) + + # Delete policies and members + group_id = qry.first().id + instance_models = [api_models.InstanceGroupPolicy, + api_models.InstanceGroupMember] + for model in instance_models: + context.session.query(model).filter_by(group_id=group_id).delete() + + qry.delete() + + @staticmethod + @db_api.api_context_manager.writer + def _add_members_in_db(context, group_uuid, members): + return _instance_group_members_add_by_uuid(context, group_uuid, + members) + def obj_load_attr(self, attrname): # NOTE(sbauza): Only hosts could be lazy-loaded right now if attrname != 'hosts': @@ -94,27 +289,39 @@ class InstanceGroup(base.NovaPersistentObject, base.NovaObject, @base.remotable_classmethod def get_by_uuid(cls, context, uuid): - db_inst = db.instance_group_get(context, uuid) - return cls._from_db_object(context, cls(), db_inst) + db_group = None + try: + db_group = cls._get_from_db_by_uuid(context, uuid) + except exception.InstanceGroupNotFound: + pass + if db_group is None: + db_group = db.instance_group_get(context, uuid) + return cls._from_db_object(context, cls(), db_group) @base.remotable_classmethod def get_by_name(cls, context, name): - # TODO(russellb) We need to get the group by name here. There's no - # db.api method for this yet. Come back and optimize this by - # adding a new query by name. This is unnecessarily expensive if a - # tenant has lots of groups. - igs = objects.InstanceGroupList.get_by_project_id(context, - context.project_id) - for ig in igs: - if ig.name == name: - return ig - - raise exception.InstanceGroupNotFound(group_uuid=name) + try: + db_group = cls._get_from_db_by_name(context, name) + except exception.InstanceGroupNotFound: + igs = InstanceGroupList._get_main_by_project_id(context, + context.project_id) + for ig in igs: + if ig.name == name: + return ig + raise exception.InstanceGroupNotFound(group_uuid=name) + return cls._from_db_object(context, cls(), db_group) @base.remotable_classmethod def get_by_instance_uuid(cls, context, instance_uuid): - db_inst = db.instance_group_get_by_instance(context, instance_uuid) - return cls._from_db_object(context, cls(), db_inst) + db_group = None + try: + db_group = cls._get_from_db_by_instance(context, instance_uuid) + except exception.InstanceGroupNotFound: + pass + if db_group is None: + db_group = db.instance_group_get_by_instance(context, + instance_uuid) + return cls._from_db_object(context, cls(), db_group) @classmethod def get_by_hint(cls, context, hint): @@ -147,9 +354,12 @@ class InstanceGroup(base.NovaPersistentObject, base.NovaObject, payload = dict(updates) payload['server_group_id'] = self.uuid - db.instance_group_update(self._context, self.uuid, updates) - db_inst = db.instance_group_get(self._context, self.uuid) - self._from_db_object(self._context, self, db_inst) + try: + db_group = self._save_in_db(self._context, self.uuid, updates) + except exception.InstanceGroupNotFound: + db.instance_group_update(self._context, self.uuid, updates) + db_group = db.instance_group_get(self._context, self.uuid) + self._from_db_object(self._context, self, db_group) compute_utils.notify_about_server_group_update(self._context, "update", payload) @@ -173,10 +383,21 @@ class InstanceGroup(base.NovaPersistentObject, base.NovaObject, policies = updates.pop('policies', None) members = updates.pop('members', None) - db_inst = db.instance_group_create(self._context, updates, - policies=policies, - members=members) - self._from_db_object(self._context, self, db_inst) + if 'uuid' not in updates: + self.uuid = uuidutils.generate_uuid() + updates['uuid'] = self.uuid + + try: + db.instance_group_get(self._context, self.uuid) + raise exception.ObjectActionError( + action='create', + reason='already created in main') + except exception.InstanceGroupNotFound: + pass + db_group = self._create_in_db(self._context, updates, + policies=policies, + members=members) + self._from_db_object(self._context, self, db_group) payload['server_group_id'] = self.uuid compute_utils.notify_about_server_group_update(self._context, "create", payload) @@ -184,7 +405,10 @@ class InstanceGroup(base.NovaPersistentObject, base.NovaObject, @base.remotable def destroy(self): payload = {'server_group_id': self.uuid} - db.instance_group_delete(self._context, self.uuid) + try: + self._destroy_in_db(self._context, self.uuid) + except exception.InstanceGroupNotFound: + db.instance_group_delete(self._context, self.uuid) self.obj_reset_changes() compute_utils.notify_about_server_group_update(self._context, "delete", payload) @@ -193,8 +417,13 @@ class InstanceGroup(base.NovaPersistentObject, base.NovaObject, def add_members(cls, context, group_uuid, instance_uuids): payload = {'server_group_id': group_uuid, 'instance_uuids': instance_uuids} - members = db.instance_group_members_add(context, group_uuid, - instance_uuids) + try: + members = cls._add_members_in_db(context, group_uuid, + instance_uuids) + members = [member['instance_uuid'] for member in members] + except exception.InstanceGroupNotFound: + members = db.instance_group_members_add(context, group_uuid, + instance_uuids) compute_utils.notify_about_server_group_update(context, "addmember", payload) return list(members) @@ -244,14 +473,32 @@ class InstanceGroupList(base.ObjectListBase, base.NovaObject): 'objects': fields.ListOfObjectsField('InstanceGroup'), } + @staticmethod + @db_api.api_context_manager.reader + def _get_from_db(context, project_id=None): + query = _instance_group_get_query(context) + if project_id is not None: + query = query.filter_by(project_id=project_id) + return query.all() + + @classmethod + def _get_main_by_project_id(cls, context, project_id): + main_db_groups = db.instance_group_get_all_by_project_id(context, + project_id) + return base.obj_make_list(context, cls(context), objects.InstanceGroup, + main_db_groups) + @base.remotable_classmethod def get_by_project_id(cls, context, project_id): - groups = db.instance_group_get_all_by_project_id(context, project_id) + api_db_groups = cls._get_from_db(context, project_id=project_id) + main_db_groups = db.instance_group_get_all_by_project_id(context, + project_id) return base.obj_make_list(context, cls(context), objects.InstanceGroup, - groups) + api_db_groups + main_db_groups) @base.remotable_classmethod def get_all(cls, context): - groups = db.instance_group_get_all(context) + api_db_groups = cls._get_from_db(context) + main_db_groups = db.instance_group_get_all(context) return base.obj_make_list(context, cls(context), objects.InstanceGroup, - groups) + api_db_groups + main_db_groups) diff --git a/nova/tests/functional/db/test_instance_group.py b/nova/tests/functional/db/test_instance_group.py new file mode 100644 index 000000000000..038d222e092f --- /dev/null +++ b/nova/tests/functional/db/test_instance_group.py @@ -0,0 +1,185 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +from oslo_versionedobjects import fixture as ovo_fixture + +from nova import context +from nova.db.sqlalchemy import api as db_api +from nova import exception +from nova import objects +from nova.objects import base +from nova import test +from nova.tests import uuidsentinel as uuids + + +class InstanceGroupObjectTestCase(test.TestCase): + def setUp(self): + super(InstanceGroupObjectTestCase, self).setUp() + self.context = context.RequestContext('fake-user', 'fake-project') + + def _api_group(self, **values): + group = objects.InstanceGroup(context=self.context, + user_id=self.context.user_id, + project_id=self.context.project_id, + name='foogroup', + policies=['foo1', 'foo2'], + members=['memberfoo']) + group.update(values) + group.create() + return group + + def _main_group(self, policies=None, members=None, **values): + vals = { + 'user_id': self.context.user_id, + 'project_id': self.context.project_id, + 'name': 'foogroup', + } + vals.update(values) + policies = policies or ['foo1', 'foo2'] + members = members or ['memberfoo'] + return db_api.instance_group_create(self.context, vals, + policies=policies, + members=members) + + def test_create(self): + create_group = self._api_group() + db_group = create_group._get_from_db_by_uuid(self.context, + create_group.uuid) + ovo_fixture.compare_obj(self, create_group, db_group, + allow_missing=('deleted', 'deleted_at')) + + def test_create_duplicate_in_main(self): + self._main_group(uuid=uuids.main) + self.assertRaises(exception.ObjectActionError, + self._api_group, uuid=uuids.main) + + def test_destroy(self): + create_group = self._api_group() + create_group.destroy() + self.assertRaises(exception.InstanceGroupNotFound, + create_group._get_from_db_by_uuid, self.context, + create_group.uuid) + + def test_destroy_main(self): + db_group = self._main_group() + create_group = objects.InstanceGroup._from_db_object( + self.context, objects.InstanceGroup(), db_group) + create_group.destroy() + self.assertRaises(exception.InstanceGroupNotFound, + db_api.instance_group_get, self.context, + create_group.uuid) + + @mock.patch('nova.compute.utils.notify_about_server_group_update') + def test_save(self, _mock_notify): + create_group = self._api_group() + create_group.policies = ['bar1', 'bar2'] + create_group.members = ['memberbar1', 'memberbar2'] + create_group.name = 'anewname' + create_group.save() + db_group = create_group._get_from_db_by_uuid(self.context, + create_group.uuid) + ovo_fixture.compare_obj(self, create_group, db_group, + allow_missing=('deleted', 'deleted_at')) + + @mock.patch('nova.compute.utils.notify_about_server_group_update') + def test_save_main(self, _mock_notify): + db_group = self._main_group() + create_group = objects.InstanceGroup._from_db_object( + self.context, objects.InstanceGroup(), db_group) + create_group.policies = ['bar1', 'bar2'] + create_group.members = ['memberbar1', 'memberbar2'] + create_group.name = 'anewname' + create_group.save() + db_group = db_api.instance_group_get(self.context, create_group.uuid) + ovo_fixture.compare_obj(self, create_group, db_group) + + def test_add_members(self): + create_group = self._api_group() + new_member = ['memberbar'] + objects.InstanceGroup.add_members(self.context, create_group.uuid, + new_member) + db_group = create_group._get_from_db_by_uuid(self.context, + create_group.uuid) + self.assertEqual(create_group.members + new_member, db_group.members) + + def test_add_members_main(self): + db_group = self._main_group() + create_group = objects.InstanceGroup._from_db_object( + self.context, objects.InstanceGroup(), db_group) + new_member = ['memberbar'] + objects.InstanceGroup.add_members(self.context, create_group.uuid, + new_member) + db_group = db_api.instance_group_get(self.context, create_group.uuid) + self.assertEqual(create_group.members + new_member, db_group.members) + + def test_add_members_to_group_with_no_members(self): + create_group = self._api_group(members=[]) + new_member = ['memberbar'] + objects.InstanceGroup.add_members(self.context, create_group.uuid, + new_member) + db_group = create_group._get_from_db_by_uuid(self.context, + create_group.uuid) + self.assertEqual(new_member, db_group.members) + + def test_get_by_uuid(self): + create_group = self._api_group() + get_group = objects.InstanceGroup.get_by_uuid(self.context, + create_group.uuid) + self.assertTrue(base.obj_equal_prims(create_group, get_group)) + + def test_get_by_uuid_main(self): + db_group = self._main_group() + get_group = objects.InstanceGroup.get_by_uuid(self.context, + db_group.uuid) + ovo_fixture.compare_obj(self, get_group, db_group) + + def test_get_by_name(self): + create_group = self._api_group() + get_group = objects.InstanceGroup.get_by_name(self.context, + create_group.name) + self.assertTrue(base.obj_equal_prims(create_group, get_group)) + + def test_get_by_name_main(self): + db_group = self._main_group() + get_group = objects.InstanceGroup.get_by_name(self.context, + db_group.name) + ovo_fixture.compare_obj(self, get_group, db_group) + + def test_get_by_instance_uuid(self): + create_group = self._api_group(members=[uuids.instance]) + get_group = objects.InstanceGroup.get_by_instance_uuid(self.context, + uuids.instance) + self.assertTrue(base.obj_equal_prims(create_group, get_group)) + + def test_get_by_instance_uuid_main(self): + db_group = self._main_group(members=[uuids.instance]) + get_group = objects.InstanceGroup.get_by_instance_uuid(self.context, + uuids.instance) + ovo_fixture.compare_obj(self, get_group, db_group) + + def test_get_by_project_id(self): + create_group = self._api_group() + db_group = self._main_group() + get_groups = objects.InstanceGroupList.get_by_project_id( + self.context, self.context.project_id) + self.assertEqual(2, len(get_groups)) + self.assertTrue(base.obj_equal_prims(create_group, get_groups[0])) + ovo_fixture.compare_obj(self, get_groups[1], db_group) + + def test_get_all(self): + create_group = self._api_group() + db_group = self._main_group() + get_groups = objects.InstanceGroupList.get_all(self.context) + self.assertEqual(2, len(get_groups)) + self.assertTrue(base.obj_equal_prims(create_group, get_groups[0])) + ovo_fixture.compare_obj(self, get_groups[1], db_group) diff --git a/nova/tests/unit/objects/test_instance_group.py b/nova/tests/unit/objects/test_instance_group.py index 26c92632342d..a706a76dedf5 100644 --- a/nova/tests/unit/objects/test_instance_group.py +++ b/nova/tests/unit/objects/test_instance_group.py @@ -45,10 +45,12 @@ _INST_GROUP_DB = { class _TestInstanceGroupObject(object): @mock.patch('nova.db.instance_group_get', return_value=_INST_GROUP_DB) - def test_get_by_uuid(self, mock_db_get): - obj = objects.InstanceGroup.get_by_uuid(mock.sentinel.ctx, + @mock.patch('nova.objects.InstanceGroup._get_from_db_by_uuid', + side_effect=exception.InstanceGroupNotFound(group_uuid=_DB_UUID)) + def test_get_by_uuid_main(self, mock_api_get, mock_db_get): + obj = objects.InstanceGroup.get_by_uuid(self.context, _DB_UUID) - mock_db_get.assert_called_once_with(mock.sentinel.ctx, _DB_UUID) + mock_db_get.assert_called_once_with(self.context, _DB_UUID) self.assertEqual(_INST_GROUP_DB['members'], obj.members) self.assertEqual(_INST_GROUP_DB['policies'], obj.policies) self.assertEqual(_DB_UUID, obj.uuid) @@ -58,18 +60,21 @@ class _TestInstanceGroupObject(object): @mock.patch('nova.db.instance_group_get_by_instance', return_value=_INST_GROUP_DB) - def test_get_by_instance_uuid(self, mock_db_get): + @mock.patch('nova.objects.InstanceGroup._get_from_db_by_instance') + def test_get_by_instance_uuid_main(self, mock_api_get, mock_db_get): + error = exception.InstanceGroupNotFound(group_uuid='') + mock_api_get.side_effect = error objects.InstanceGroup.get_by_instance_uuid( - mock.sentinel.ctx, mock.sentinel.instance_uuid) + self.context, mock.sentinel.instance_uuid) mock_db_get.assert_called_once_with( - mock.sentinel.ctx, mock.sentinel.instance_uuid) + self.context, mock.sentinel.instance_uuid) @mock.patch('nova.db.instance_group_get') def test_refresh(self, mock_db_get): changed_group = copy.deepcopy(_INST_GROUP_DB) changed_group['name'] = 'new_name' mock_db_get.side_effect = [_INST_GROUP_DB, changed_group] - obj = objects.InstanceGroup.get_by_uuid(mock.sentinel.ctx, + obj = objects.InstanceGroup.get_by_uuid(self.context, _DB_UUID) self.assertEqual(_INST_GROUP_DB['name'], obj.name) obj.refresh() @@ -79,22 +84,22 @@ class _TestInstanceGroupObject(object): @mock.patch('nova.compute.utils.notify_about_server_group_update') @mock.patch('nova.db.instance_group_update') @mock.patch('nova.db.instance_group_get') - def test_save(self, mock_db_get, mock_db_update, mock_notify): + def test_save_main(self, mock_db_get, mock_db_update, mock_notify): changed_group = copy.deepcopy(_INST_GROUP_DB) changed_group['name'] = 'new_name' mock_db_get.side_effect = [_INST_GROUP_DB, changed_group] - obj = objects.InstanceGroup.get_by_uuid(mock.sentinel.ctx, + obj = objects.InstanceGroup.get_by_uuid(self.context, _DB_UUID) self.assertEqual(obj.name, 'fake_name') obj.name = 'new_name' obj.policies = ['policy1'] # Remove policy 2 obj.members = ['instance_id1'] # Remove member 2 obj.save() - mock_db_update.assert_called_once_with(mock.sentinel.ctx, _DB_UUID, + mock_db_update.assert_called_once_with(self.context, _DB_UUID, {'name': 'new_name', 'members': ['instance_id1'], 'policies': ['policy1']}) - mock_notify.assert_called_once_with(mock.sentinel.ctx, "update", + mock_notify.assert_called_once_with(self.context, "update", {'name': 'new_name', 'members': ['instance_id1'], 'policies': ['policy1'], @@ -103,10 +108,10 @@ class _TestInstanceGroupObject(object): @mock.patch('nova.compute.utils.notify_about_server_group_update') @mock.patch('nova.db.instance_group_update') @mock.patch('nova.db.instance_group_get') - def test_save_without_hosts(self, mock_db_get, mock_db_update, - mock_notify): + def test_save_without_hosts_main(self, mock_db_get, mock_db_update, + mock_notify): mock_db_get.side_effect = [_INST_GROUP_DB, _INST_GROUP_DB] - obj = objects.InstanceGroup.get_by_uuid(mock.sentinel.ctx, _DB_UUID) + obj = objects.InstanceGroup.get_by_uuid(self.context, _DB_UUID) obj.hosts = ['fake-host1'] self.assertRaises(exception.InstanceGroupSaveException, obj.save) @@ -118,9 +123,10 @@ class _TestInstanceGroupObject(object): self.assertFalse(mock_notify.called) @mock.patch('nova.compute.utils.notify_about_server_group_update') - @mock.patch('nova.db.instance_group_create', return_value=_INST_GROUP_DB) + @mock.patch('nova.objects.InstanceGroup._create_in_db', + return_value=_INST_GROUP_DB) def test_create(self, mock_db_create, mock_notify): - obj = objects.InstanceGroup(context=mock.sentinel.ctx) + obj = objects.InstanceGroup(context=self.context) obj.uuid = _DB_UUID obj.name = _INST_GROUP_DB['name'] obj.user_id = _INST_GROUP_DB['user_id'] @@ -133,7 +139,7 @@ class _TestInstanceGroupObject(object): obj.deleted = False obj.create() mock_db_create.assert_called_once_with( - mock.sentinel.ctx, + self.context, {'uuid': _DB_UUID, 'name': _INST_GROUP_DB['name'], 'user_id': _INST_GROUP_DB['user_id'], @@ -146,7 +152,7 @@ class _TestInstanceGroupObject(object): members=_INST_GROUP_DB['members'], policies=_INST_GROUP_DB['policies']) mock_notify.assert_called_once_with( - mock.sentinel.ctx, "create", + self.context, "create", {'uuid': _DB_UUID, 'name': _INST_GROUP_DB['name'], 'user_id': _INST_GROUP_DB['user_id'], @@ -162,60 +168,64 @@ class _TestInstanceGroupObject(object): self.assertRaises(exception.ObjectActionError, obj.create) @mock.patch('nova.compute.utils.notify_about_server_group_update') - @mock.patch('nova.db.instance_group_delete') + @mock.patch('nova.objects.InstanceGroup._destroy_in_db') def test_destroy(self, mock_db_delete, mock_notify): - obj = objects.InstanceGroup(context=mock.sentinel.ctx) + obj = objects.InstanceGroup(context=self.context) obj.uuid = _DB_UUID obj.destroy() - mock_db_delete.assert_called_once_with(mock.sentinel.ctx, _DB_UUID) - mock_notify.assert_called_once_with(mock.sentinel.ctx, "delete", + mock_db_delete.assert_called_once_with(self.context, _DB_UUID) + mock_notify.assert_called_once_with(self.context, "delete", {'server_group_id': _DB_UUID}) @mock.patch('nova.compute.utils.notify_about_server_group_update') - @mock.patch('nova.db.instance_group_members_add') + @mock.patch('nova.objects.InstanceGroup._add_members_in_db') def test_add_members(self, mock_members_add_db, mock_notify): - mock_members_add_db.return_value = [mock.sentinel.members] - members = objects.InstanceGroup.add_members(mock.sentinel.ctx, + fake_member_models = [{'instance_uuid': mock.sentinel.uuid}] + fake_member_uuids = [mock.sentinel.uuid] + mock_members_add_db.return_value = fake_member_models + members = objects.InstanceGroup.add_members(self.context, _DB_UUID, - mock.sentinel.members) - self.assertEqual([mock.sentinel.members], members) + fake_member_uuids) + self.assertEqual(fake_member_uuids, members) mock_members_add_db.assert_called_once_with( - mock.sentinel.ctx, + self.context, _DB_UUID, - mock.sentinel.members) + fake_member_uuids) mock_notify.assert_called_once_with( - mock.sentinel.ctx, "addmember", - {'instance_uuids': mock.sentinel.members, + self.context, "addmember", + {'instance_uuids': fake_member_uuids, 'server_group_id': _DB_UUID}) @mock.patch('nova.objects.InstanceList.get_by_filters') - @mock.patch('nova.db.instance_group_get', return_value=_INST_GROUP_DB) + @mock.patch('nova.objects.InstanceGroup._get_from_db_by_uuid', + return_value=_INST_GROUP_DB) def test_count_members_by_user(self, mock_get_db, mock_il_get): mock_il_get.return_value = [mock.ANY] - obj = objects.InstanceGroup.get_by_uuid(mock.sentinel.ctx, _DB_UUID) + obj = objects.InstanceGroup.get_by_uuid(self.context, _DB_UUID) expected_filters = { 'uuid': ['instance_id1', 'instance_id2'], 'user_id': 'fake_user', 'deleted': False } self.assertEqual(1, obj.count_members_by_user('fake_user')) - mock_il_get.assert_called_once_with(mock.sentinel.ctx, + mock_il_get.assert_called_once_with(self.context, filters=expected_filters) @mock.patch('nova.objects.InstanceList.get_by_filters') - @mock.patch('nova.db.instance_group_get', return_value=_INST_GROUP_DB) + @mock.patch('nova.objects.InstanceGroup._get_from_db_by_uuid', + return_value=_INST_GROUP_DB) def test_get_hosts(self, mock_get_db, mock_il_get): mock_il_get.return_value = [objects.Instance(host='host1'), objects.Instance(host='host2'), objects.Instance(host=None)] - obj = objects.InstanceGroup.get_by_uuid(mock.sentinel.ctx, _DB_UUID) + obj = objects.InstanceGroup.get_by_uuid(self.context, _DB_UUID) hosts = obj.get_hosts() self.assertEqual(['instance_id1', 'instance_id2'], obj.members) expected_filters = { 'uuid': ['instance_id1', 'instance_id2'], 'deleted': False } - mock_il_get.assert_called_once_with(mock.sentinel.ctx, + mock_il_get.assert_called_once_with(self.context, filters=expected_filters) self.assertEqual(2, len(hosts)) self.assertIn('host1', hosts) @@ -228,12 +238,11 @@ class _TestInstanceGroupObject(object): 'uuid': set(['instance_id2']), 'deleted': False } - mock_il_get.assert_called_once_with(mock.sentinel.ctx, + mock_il_get.assert_called_once_with(self.context, filters=expected_filters) - @mock.patch('nova.db.instance_group_get', return_value=_INST_GROUP_DB) - def test_obj_make_compatible(self, mock_db_get): - obj = objects.InstanceGroup.get_by_uuid(mock.sentinel.ctx, _DB_UUID) + def test_obj_make_compatible(self): + obj = objects.InstanceGroup(self.context, **_INST_GROUP_DB) obj_primitive = obj.obj_to_primitive() self.assertNotIn('metadetails', obj_primitive) obj.obj_make_compatible(obj_primitive, '1.6') @@ -244,14 +253,14 @@ class _TestInstanceGroupObject(object): mock_get_by_filt.return_value = [objects.Instance(host='host1'), objects.Instance(host='host2')] - obj = objects.InstanceGroup(mock.sentinel.ctx, members=['uuid1']) + obj = objects.InstanceGroup(self.context, members=['uuid1']) self.assertEqual(2, len(obj.hosts)) self.assertIn('host1', obj.hosts) self.assertIn('host2', obj.hosts) self.assertNotIn('hosts', obj.obj_what_changed()) def test_load_anything_else_but_hosts(self): - obj = objects.InstanceGroup(mock.sentinel.ctx) + obj = objects.InstanceGroup(self.context) self.assertRaises(exception.ObjectActionError, getattr, obj, 'members') @@ -283,22 +292,29 @@ def _mock_db_list_get(*args): class _TestInstanceGroupListObject(object): @mock.patch('nova.db.instance_group_get_all') - def test_list_all(self, mock_db_get): + @mock.patch('nova.objects.InstanceGroupList._get_from_db') + def test_list_all_main(self, mock_api_get, mock_db_get): + mock_api_get.return_value = [] mock_db_get.side_effect = _mock_db_list_get - inst_list = objects.InstanceGroupList.get_all(mock.sentinel.ctx) + inst_list = objects.InstanceGroupList.get_all(self.context) self.assertEqual(4, len(inst_list.objects)) - mock_db_get.assert_called_once_with(mock.sentinel.ctx) + mock_db_get.assert_called_once_with(self.context) @mock.patch('nova.db.instance_group_get_all_by_project_id') - def test_list_by_project_id(self, mock_db_get): + @mock.patch('nova.objects.InstanceGroupList._get_from_db') + def test_list_by_project_id_main(self, mock_api_get, mock_db_get): + mock_api_get.return_value = [] mock_db_get.side_effect = _mock_db_list_get objects.InstanceGroupList.get_by_project_id( - mock.sentinel.ctx, mock.sentinel.project_id) + self.context, mock.sentinel.project_id) mock_db_get.assert_called_once_with( - mock.sentinel.ctx, mock.sentinel.project_id) + self.context, mock.sentinel.project_id) @mock.patch('nova.db.instance_group_get_all_by_project_id') - def test_get_by_name(self, mock_db_get): + @mock.patch('nova.objects.InstanceGroup._get_from_db_by_name') + def test_get_by_name_main(self, mock_api_get, mock_db_get): + error = exception.InstanceGroupNotFound(group_uuid='f1') + mock_api_get.side_effect = error mock_db_get.side_effect = _mock_db_list_get # Need the project_id value set, otherwise we'd use mock.sentinel mock_ctx = mock.MagicMock() @@ -313,10 +329,10 @@ class _TestInstanceGroupListObject(object): @mock.patch('nova.objects.InstanceGroup.get_by_uuid') @mock.patch('nova.objects.InstanceGroup.get_by_name') def test_get_by_hint(self, mock_name, mock_uuid): - objects.InstanceGroup.get_by_hint(mock.sentinel.ctx, _DB_UUID) - mock_uuid.assert_called_once_with(mock.sentinel.ctx, _DB_UUID) - objects.InstanceGroup.get_by_hint(mock.sentinel.ctx, 'name') - mock_name.assert_called_once_with(mock.sentinel.ctx, 'name') + objects.InstanceGroup.get_by_hint(self.context, _DB_UUID) + mock_uuid.assert_called_once_with(self.context, _DB_UUID) + objects.InstanceGroup.get_by_hint(self.context, 'name') + mock_name.assert_called_once_with(self.context, 'name') class TestInstanceGroupListObject(test_objects._LocalTest,