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,