From 501ff418df4c08e52a697772200aa08dd67a4a43 Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Thu, 9 May 2013 13:42:44 +0000 Subject: Nova instance group DB support DB support for blueprint instance-group-api-extension Change-Id: I615af9826ef61fd63d4cd8017908f943969bf177 --- nova/db/api.py | 90 +++++ nova/db/sqlalchemy/api.py | 354 ++++++++++++++++++++ .../versions/187_add_instance_groups.py | 121 +++++++ nova/db/sqlalchemy/models.py | 69 ++++ nova/exception.py | 22 ++ nova/tests/db/test_db_api.py | 364 +++++++++++++++++++++ nova/tests/db/test_migrations.py | 44 +++ 7 files changed, 1064 insertions(+) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/187_add_instance_groups.py diff --git a/nova/db/api.py b/nova/db/api.py index 8a7c6dc48..7ecf6dfc0 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -708,6 +708,96 @@ def instance_remove_security_group(context, instance_id, security_group_id): security_group_id) +#################### + + +def instance_group_create(context, values, policies=None, metadata=None, + members=None): + """Create a new group with metadata. + + Each group will receive a unique uuid. This will be used for access to the + group. + """ + return IMPL.instance_group_create(context, values, policies, metadata, + members) + + +def instance_group_get(context, group_uuid): + """Get a specific group by id.""" + return IMPL.instance_group_get(context, group_uuid) + + +def instance_group_update(context, group_uuid, values): + """Update the attributes of an group.""" + return IMPL.instance_group_update(context, group_uuid, values) + + +def instance_group_delete(context, group_uuid): + """Delete an group.""" + return IMPL.instance_group_delete(context, group_uuid) + + +def instance_group_get_all(context): + """Get all groups.""" + return IMPL.instance_group_get_all(context) + + +def instance_group_get_all_by_project_id(context, project_id): + """Get all groups for a specific project_id.""" + return IMPL.instance_group_get_all_by_project_id(context, project_id) + + +def instance_group_metadata_add(context, group_uuid, metadata, + set_delete=False): + """Add metadata to the group.""" + return IMPL.instance_group_metadata_add(context, group_uuid, metadata, + set_delete) + + +def instance_group_metadata_delete(context, group_uuid, key): + """Delete metadata from the group.""" + return IMPL.instance_group_metadata_delete(context, group_uuid, key) + + +def instance_group_metadata_get(context, group_uuid): + """Get the metadata from the group.""" + return IMPL.instance_group_metadata_get(context, group_uuid) + + +def instance_group_members_add(context, group_uuid, members, + set_delete=False): + """Add members to the group.""" + return IMPL.instance_group_members_add(context, group_uuid, members, + set_delete=set_delete) + + +def instance_group_member_delete(context, group_uuid, instance_id): + """Delete a specific member from the group.""" + return IMPL.instance_group_member_delete(context, group_uuid, instance_id) + + +def instance_group_members_get(context, group_uuid): + """Get the members from the group.""" + return IMPL.instance_group_members_get(context, group_uuid) + + +def instance_group_policies_add(context, group_uuid, policies, + set_delete=False): + """Add policies to the group.""" + return IMPL.instance_group_policies_add(context, group_uuid, policies, + set_delete=set_delete) + + +def instance_group_policy_delete(context, group_uuid, policy): + """Delete a specific policy from the group.""" + return IMPL.instance_group_policy_delete(context, group_uuid, policy) + + +def instance_group_policies_get(context, group_uuid): + """Get the policies from the group.""" + return IMPL.instance_group_policies_get(context, group_uuid) + + ################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index adacc6ead..d27f4e695 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -5187,3 +5187,357 @@ def archive_deleted_rows(context, max_rows=None): if rows_archived >= max_rows: break return rows_archived + + +#################### + + +def _instance_group_get_query(context, model_class, id_field=None, id=None, + session=None, read_deleted=None): + columns_to_join = {models.InstanceGroup: ['_policies', '_metadata', + '_members']} + query = model_query(context, model_class, session=session, + read_deleted=read_deleted) + + for c in columns_to_join.get(model_class, []): + query = query.options(joinedload(c)) + + if id and id_field: + query = query.filter(id_field == id) + + return query + + +def instance_group_create(context, values, policies=None, metadata=None, + members=None): + """Create a new group with metadata.""" + uuid = values.get('uuid', None) + if uuid is None: + uuid = uuidutils.generate_uuid() + values['uuid'] = uuid + session = get_session() + with session.begin(): + try: + group = models.InstanceGroup() + group.update(values) + group.save(session=session) + except db_exc.DBDuplicateEntry: + raise exception.InstanceGroupIdExists(group_uuid=uuid) + + # We don't want these to be lazy loaded later. We know there is + # nothing here since we just created this instance group. + group._policies = [] + group._metadata = [] + group._members = [] + if policies: + _instance_group_policies_add(context, group.id, policies, + session=session) + if metadata: + _instance_group_metadata_add(context, group.id, metadata, + session=session) + if members: + _instance_group_members_add(context, group.id, members, + session=session) + return instance_group_get(context, uuid) + + +def instance_group_get(context, group_uuid): + """Get a specific group by uuid.""" + group = _instance_group_get_query(context, + models.InstanceGroup, + models.InstanceGroup.uuid, + group_uuid).\ + first() + if not group: + raise exception.InstanceGroupNotFound(group_uuid=group_uuid) + return group + + +def instance_group_update(context, group_uuid, values): + """Update the attributes of an group. + + If values contains a metadata key, it updates the aggregate metadata + too. Similary for the policies and members. + """ + session = get_session() + with session.begin(): + group = model_query(context, + models.InstanceGroup, + session=session).\ + filter_by(uuid=group_uuid).\ + first() + if not group: + raise exception.InstanceGroupNotFound(group_uuid=group_uuid) + + policies = values.get('policies') + if policies is not None: + _instance_group_policies_add(context, + group.id, + values.pop('policies'), + set_delete=True, + session=session) + metadata = values.get('metadata') + if metadata is not None: + _instance_group_metadata_add(context, + group.id, + values.pop('metadata'), + set_delete=True, + session=session) + members = values.get('members') + if members is not None: + _instance_group_members_add(context, + group.id, + values.pop('members'), + set_delete=True, + session=session) + + group.update(values) + group.save(session=session) + if policies: + values['policies'] = policies + if metadata: + values['metadata'] = metadata + if members: + values['members'] = members + + +def instance_group_delete(context, group_uuid): + """Delete an group.""" + session = get_session() + with session.begin(): + count = _instance_group_get_query(context, + models.InstanceGroup, + models.InstanceGroup.uuid, + group_uuid, + session=session).soft_delete() + if count == 0: + raise exception.InstanceGroupNotFound(group_uuid=group_uuid) + + # Delete policies, metadata and members + instance_models = [models.InstanceGroupPolicy, + models.InstanceGroupMetadata, + models.InstanceGroupMember] + for model in instance_models: + model_query(context, model, session=session).\ + filter_by(group_id=group_uuid).\ + soft_delete() + + +def instance_group_get_all(context): + """Get all groups.""" + return _instance_group_get_query(context, models.InstanceGroup).all() + + +def instance_group_get_all_by_project_id(context, project_id): + """Get all groups.""" + return _instance_group_get_query(context, models.InstanceGroup).\ + filter_by(project_id=project_id).\ + all() + + +def _instance_group_model_get_query(context, model_class, group_uuid, + session=None, read_deleted='no'): + return model_query(context, + model_class, + read_deleted=read_deleted, + session=session).\ + filter_by(group_id=group_uuid) + + +def _instance_group_id(context, group_uuid): + """Returns the group database ID for the group UUID.""" + + result = model_query(context, + models.InstanceGroup.id, + base_model=models.InstanceGroup).\ + filter_by(uuid=group_uuid).\ + first() + if not result: + raise exception.InstanceGroupNotFound(group_uuid=group_uuid) + return result.id + + +def _instance_group_metadata_add(context, id, metadata, set_delete=False, + session=None): + if not session: + session = get_session() + + with session.begin(subtransactions=True): + all_keys = metadata.keys() + query = _instance_group_model_get_query(context, + models.InstanceGroupMetadata, + id, + session=session) + if set_delete: + query.filter(~models.InstanceGroupMetadata.key.in_(all_keys)).\ + soft_delete(synchronize_session=False) + + query = query.filter(models.InstanceGroupMetadata.key.in_(all_keys)) + already_existing_keys = set() + for meta_ref in query.all(): + key = meta_ref.key + meta_ref.update({'value': metadata[key]}) + already_existing_keys.add(key) + + for key, value in metadata.iteritems(): + if key in already_existing_keys: + continue + meta_ref = models.InstanceGroupMetadata() + meta_ref.update({'key': key, + 'value': value, + 'group_id': id}) + session.add(meta_ref) + + return metadata + + +def instance_group_metadata_add(context, group_uuid, metadata, + set_delete=False): + id = _instance_group_id(context, group_uuid) + return _instance_group_metadata_add(context, id, metadata, + set_delete=set_delete) + + +def instance_group_metadata_delete(context, group_uuid, key): + id = _instance_group_id(context, group_uuid) + count = _instance_group_get_query(context, + models.InstanceGroupMetadata, + models.InstanceGroupMetadata.group_id, + id).\ + filter_by(key=key).\ + soft_delete() + if count == 0: + raise exception.InstanceGroupMetadataNotFound(group_uuid=group_uuid, + metadata_key=key) + + +def instance_group_metadata_get(context, group_uuid): + id = _instance_group_id(context, group_uuid) + rows = model_query(context, + models.InstanceGroupMetadata.key, + models.InstanceGroupMetadata.value, + base_model=models.InstanceGroupMetadata).\ + filter_by(group_id=id).all() + return dict((r[0], r[1]) for r in rows) + + +def _instance_group_members_add(context, id, members, set_delete=False, + session=None): + if not session: + session = get_session() + + all_members = set(members) + with session.begin(subtransactions=True): + query = _instance_group_model_get_query(context, + models.InstanceGroupMember, + id, + session=session) + if set_delete: + query.filter(~models.InstanceGroupMember.instance_id.in_( + all_members)).\ + soft_delete(synchronize_session=False) + + query = query.filter( + models.InstanceGroupMember.instance_id.in_(all_members)) + already_existing = set() + for member_ref in query.all(): + already_existing.add(member_ref.instance_id) + + for instance_id in members: + if instance_id in already_existing: + continue + member_ref = models.InstanceGroupMember() + member_ref.update({'instance_id': instance_id, + 'group_id': id}) + session.add(member_ref) + + return members + + +def instance_group_members_add(context, group_uuid, members, + set_delete=False): + id = _instance_group_id(context, group_uuid) + return _instance_group_members_add(context, id, members, + set_delete=set_delete) + + +def instance_group_member_delete(context, group_uuid, instance_id): + id = _instance_group_id(context, group_uuid) + count = _instance_group_get_query(context, + models.InstanceGroupMember, + models.InstanceGroupMember.group_id, + id).\ + filter_by(instance_id=instance_id).\ + soft_delete() + if count == 0: + raise exception.InstanceGroupMemberNotFound(group_uuid=group_uuid, + instance_id=instance_id) + + +def instance_group_members_get(context, group_uuid): + id = _instance_group_id(context, group_uuid) + instances = model_query(context, + models.InstanceGroupMember.instance_id, + base_model=models.InstanceGroupMember).\ + filter_by(group_id=id).all() + return [instance[0] for instance in instances] + + +def _instance_group_policies_add(context, id, policies, set_delete=False, + session=None): + if not session: + session = get_session() + + allpols = set(policies) + with session.begin(subtransactions=True): + query = _instance_group_model_get_query(context, + models.InstanceGroupPolicy, + id, + session=session) + if set_delete: + query.filter(~models.InstanceGroupPolicy.policy.in_(allpols)).\ + soft_delete(synchronize_session=False) + + query = query.filter(models.InstanceGroupPolicy.policy.in_(allpols)) + already_existing = set() + for policy_ref in query.all(): + already_existing.add(policy_ref.policy) + + for policy in policies: + if policy in already_existing: + continue + policy_ref = models.InstanceGroupPolicy() + policy_ref.update({'policy': policy, + 'group_id': id}) + session.add(policy_ref) + + return policies + + +def instance_group_policies_add(context, group_uuid, policies, + set_delete=False): + id = _instance_group_id(context, group_uuid) + return _instance_group_policies_add(context, id, policies, + set_delete=set_delete) + + +def instance_group_policy_delete(context, group_uuid, policy): + id = _instance_group_id(context, group_uuid) + count = _instance_group_get_query(context, + models.InstanceGroupPolicy, + models.InstanceGroupPolicy.group_id, + id).\ + filter_by(policy=policy).\ + soft_delete() + if count == 0: + raise exception.InstanceGroupPolicyNotFound(group_uuid=group_uuid, + policy=policy) + + +def instance_group_policies_get(context, group_uuid): + id = _instance_group_id(context, group_uuid) + policies = model_query(context, + models.InstanceGroupPolicy.policy, + base_model=models.InstanceGroupPolicy).\ + filter_by(group_id=id).all() + return [policy[0] for policy in policies] diff --git a/nova/db/sqlalchemy/migrate_repo/versions/187_add_instance_groups.py b/nova/db/sqlalchemy/migrate_repo/versions/187_add_instance_groups.py new file mode 100644 index 000000000..227188e90 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/187_add_instance_groups.py @@ -0,0 +1,121 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack Foundation +# +# 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. + +from migrate.changeset import UniqueConstraint +from sqlalchemy import Column +from sqlalchemy import DateTime +from sqlalchemy import ForeignKey +from sqlalchemy import Index +from sqlalchemy import Integer +from sqlalchemy import MetaData +from sqlalchemy import String +from sqlalchemy import Table + +from nova.db.sqlalchemy import api as db +from nova.db.sqlalchemy import utils + + +def upgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + groups = Table('instance_groups', meta, + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('deleted_at', DateTime), + Column('deleted', Integer), + Column('id', Integer, primary_key=True, nullable=False), + Column('user_id', String(length=255)), + Column('project_id', String(length=255)), + Column('uuid', String(length=36), nullable=False), + Column('name', String(length=255)), + UniqueConstraint('uuid', 'deleted', + name='uniq_instance_groups0uuid0deleted'), + mysql_engine='InnoDB', + mysql_charset='utf8', + ) + + group_metadata = Table('instance_group_metadata', meta, + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('deleted_at', DateTime), + Column('deleted', Integer), + Column('id', Integer, primary_key=True, nullable=False), + Column('key', String(length=255)), + Column('value', String(length=255)), + Column('group_id', Integer, ForeignKey('instance_groups.id'), + nullable=False), + mysql_engine='InnoDB', + mysql_charset='utf8', + ) + + group_policy = Table('instance_group_policy', meta, + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('deleted_at', DateTime), + Column('deleted', Integer), + Column('id', Integer, primary_key=True, nullable=False), + Column('policy', String(length=255)), + Column('group_id', Integer, ForeignKey('instance_groups.id'), + nullable=False), + mysql_engine='InnoDB', + mysql_charset='utf8', + ) + + group_member = Table('instance_group_member', meta, + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('deleted_at', DateTime), + Column('deleted', Integer), + Column('id', Integer, primary_key=True, nullable=False), + Column('instance_id', String(length=255)), + Column('group_id', Integer, ForeignKey('instance_groups.id'), + nullable=False), + mysql_engine='InnoDB', + mysql_charset='utf8', + ) + + tables = [groups, group_metadata, group_policy, group_member] + + # create all of the tables + for table in tables: + table.create() + utils.create_shadow_table(migrate_engine, table=table) + + indexes = [ + Index('instance_group_metadata_key_idx', group_metadata.c.key), + Index('instance_group_member_instance_idx', + group_member.c.instance_id), + Index('instance_group_policy_policy_idx', group_policy.c.policy) + ] + + # Common indexes + if migrate_engine.name == 'mysql' or migrate_engine.name == 'postgresql': + for index in indexes: + index.create(migrate_engine) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + table_names = ['instance_group_member', 'instance_group_policy', + 'instance_group_metadata', 'instance_groups'] + for name in table_names: + table = Table(name, meta, autoload=True) + table.drop() + table = Table(db._SHADOW_TABLE_PREFIX + name, meta, autoload=True) + table.drop() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 386fcbdad..28fe36a0d 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -1039,3 +1039,72 @@ class TaskLog(BASE, NovaBase): message = Column(String(255), nullable=False) task_items = Column(Integer(), default=0) errors = Column(Integer(), default=0) + + +class InstanceGroupMember(BASE, NovaBase): + """Represents the members for an instance group.""" + __tablename__ = 'instance_group_member' + id = Column(Integer, primary_key=True, nullable=False) + instance_id = Column(String(255), nullable=False) + group_id = Column(Integer, ForeignKey('instance_groups.id'), + nullable=False) + + +class InstanceGroupPolicy(BASE, NovaBase): + """Represents the policy type for an instance group.""" + __tablename__ = 'instance_group_policy' + id = Column(Integer, primary_key=True, nullable=False) + policy = Column(String(255), nullable=False) + group_id = Column(Integer, ForeignKey('instance_groups.id'), + nullable=False) + + +class InstanceGroupMetadata(BASE, NovaBase): + """Represents a key/value pair for an instance group.""" + __tablename__ = 'instance_group_metadata' + id = Column(Integer, primary_key=True, nullable=False) + key = Column(String(255), nullable=False) + value = Column(String(255), nullable=False) + group_id = Column(Integer, ForeignKey('instance_groups.id'), + nullable=False) + + +class InstanceGroup(BASE, NovaBase): + """Represents an instance group. + + A group will maintain a collection of instances and the relationship + between them. + """ + + __tablename__ = 'instance_groups' + __table_args__ = (schema.UniqueConstraint("uuid", "deleted"), ) + + id = Column(Integer, primary_key=True, autoincrement=True) + user_id = Column(String(255)) + project_id = Column(String(255)) + uuid = Column(String(36), nullable=False) + name = Column(String(255)) + _policies = relationship(InstanceGroupPolicy, primaryjoin='and_(' + 'InstanceGroup.id == InstanceGroupPolicy.group_id,' + 'InstanceGroupPolicy.deleted == 0,' + 'InstanceGroup.deleted == 0)') + _metadata = relationship(InstanceGroupMetadata, primaryjoin='and_(' + 'InstanceGroup.id == InstanceGroupMetadata.group_id,' + 'InstanceGroupMetadata.deleted == 0,' + 'InstanceGroup.deleted == 0)') + _members = relationship(InstanceGroupMember, primaryjoin='and_(' + 'InstanceGroup.id == InstanceGroupMember.group_id,' + 'InstanceGroupMember.deleted == 0,' + 'InstanceGroup.deleted == 0)') + + @property + def policies(self): + return [p.policy for p in self._policies] + + @property + def metadetails(self): + return dict((m.key, m.value) for m in self._metadata) + + @property + def members(self): + return [m.instance_id for m in self._members] diff --git a/nova/exception.py b/nova/exception.py index 905ddf2da..d8ad08131 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -1235,3 +1235,25 @@ class IncompatibleObjectVersion(NovaException): class CoreAPIMissing(NovaException): message = _("Core API extensions are missing: %(missing_apis)s") + + +class InstanceGroupNotFound(NotFound): + message = _("Instance group %(group_uuid)s could not be found.") + + +class InstanceGroupIdExists(Duplicate): + message = _("Instance group %(group_uuid)s already exists.") + + +class InstanceGroupMetadataNotFound(NotFound): + message = _("Instance group %(group_uuid)s has no metadata with " + "key %(metadata_key)s.") + + +class InstanceGroupMemberNotFound(NotFound): + message = _("Instance group %(group_uuid)s has no member with " + "id %(instance_id)s.") + + +class InstanceGroupPolicyNotFound(NotFound): + message = _("Instance group %(group_uuid)s has no policy %(policy)s.") diff --git a/nova/tests/db/test_db_api.py b/nova/tests/db/test_db_api.py index 04b5bdc33..711f81e52 100644 --- a/nova/tests/db/test_db_api.py +++ b/nova/tests/db/test_db_api.py @@ -4950,3 +4950,367 @@ class ArchiveTestCase(test.TestCase): siim_rows = self.conn.execute(qsiim).fetchall() si_rows = self.conn.execute(qsi).fetchall() self.assertEqual(len(siim_rows) + len(si_rows), 8) + + +class InstanceGroupDBApiTestCase(test.TestCase, ModelsObjectComparatorMixin): + def setUp(self): + super(InstanceGroupDBApiTestCase, self).setUp() + self.user_id = 'fake_user' + self.project_id = 'fake_project' + self.context = context.RequestContext(self.user_id, self.project_id) + + def _get_default_values(self): + return {'name': 'fake_name', + 'user_id': self.user_id, + 'project_id': self.project_id} + + def _create_instance_group(self, context, values, policies=None, + metadata=None, members=None): + return db.instance_group_create(context, values, policies=policies, + metadata=metadata, members=members) + + def test_instance_group_create_no_key(self): + values = self._get_default_values() + result = self._create_instance_group(self.context, values) + ignored_keys = ['id', 'uuid', 'deleted', 'deleted_at', 'updated_at', + 'created_at'] + self._assertEqualObjects(result, values, ignored_keys) + self.assertTrue(uuidutils.is_uuid_like(result['uuid'])) + + def test_instance_group_create_with_key(self): + values = self._get_default_values() + values['uuid'] = 'fake_id' + result = self._create_instance_group(self.context, values) + ignored_keys = ['id', 'deleted', 'deleted_at', 'updated_at', + 'created_at'] + self._assertEqualObjects(result, values, ignored_keys) + + def test_instance_group_create_with_same_key(self): + values = self._get_default_values() + values['uuid'] = 'fake_id' + result = self._create_instance_group(self.context, values) + self.assertRaises(exception.InstanceGroupIdExists, + self._create_instance_group, self.context, values) + + def test_instance_group_get(self): + values = self._get_default_values() + result1 = self._create_instance_group(self.context, values) + result2 = db.instance_group_get(self.context, result1['uuid']) + self._assertEqualObjects(result1, result2) + + def test_instance_group_update_simple(self): + values = self._get_default_values() + result1 = self._create_instance_group(self.context, values) + values = {'name': 'new_name', 'user_id': 'new_user', + 'project_id': 'new_project'} + db.instance_group_update(self.context, result1['uuid'], + values) + result2 = db.instance_group_get(self.context, result1['uuid']) + self.assertEquals(result1['uuid'], result2['uuid']) + ignored_keys = ['id', 'uuid', 'deleted', 'deleted_at', 'updated_at', + 'created_at'] + self._assertEqualObjects(result2, values, ignored_keys) + + def test_instance_group_delete(self): + values = self._get_default_values() + result = self._create_instance_group(self.context, values) + db.instance_group_delete(self.context, result['uuid']) + self.assertRaises(exception.InstanceGroupNotFound, + db.instance_group_get, self.context, result['uuid']) + + def test_instance_group_get_all(self): + groups = db.instance_group_get_all(self.context) + self.assertEquals(0, len(groups)) + value = self._get_default_values() + result1 = self._create_instance_group(self.context, value) + groups = db.instance_group_get_all(self.context) + self.assertEquals(1, len(groups)) + value = self._get_default_values() + result2 = self._create_instance_group(self.context, value) + groups = db.instance_group_get_all(self.context) + results = [result1, result2] + self._assertEqualListsOfObjects(results, groups) + + def test_instance_group_get_all_by_project_id(self): + groups = db.instance_group_get_all_by_project_id(self.context, + 'invalid_project_id') + self.assertEquals(0, len(groups)) + values = self._get_default_values() + result1 = self._create_instance_group(self.context, values) + groups = db.instance_group_get_all_by_project_id(self.context, + 'fake_project') + self.assertEquals(1, len(groups)) + values = self._get_default_values() + values['project_id'] = 'new_project_id' + result2 = self._create_instance_group(self.context, values) + groups = db.instance_group_get_all(self.context) + results = [result1, result2] + self._assertEqualListsOfObjects(results, groups) + projects = [{'name': 'fake_project', 'value': [result1]}, + {'name': 'new_project_id', 'value': [result2]}] + for project in projects: + groups = db.instance_group_get_all_by_project_id(self.context, + project['name']) + self._assertEqualListsOfObjects(project['value'], groups) + + def test_instance_group_update(self): + values = self._get_default_values() + result = self._create_instance_group(self.context, values) + ignored_keys = ['id', 'uuid', 'deleted', 'deleted_at', 'updated_at', + 'created_at'] + self._assertEqualObjects(result, values, ignored_keys) + self.assertTrue(uuidutils.is_uuid_like(result['uuid'])) + id = result['uuid'] + values = self._get_default_values() + values['name'] = 'new_fake_name' + db.instance_group_update(self.context, id, values) + result = db.instance_group_get(self.context, id) + self.assertEquals(result['name'], 'new_fake_name') + # update metadata + values = self._get_default_values() + metadataInput = {'key11': 'value1', + 'key12': 'value2'} + values['metadata'] = metadataInput + db.instance_group_update(self.context, id, values) + result = db.instance_group_get(self.context, id) + metadata = result['metadetails'] + self._assertEqualObjects(metadata, metadataInput) + # update update members + values = self._get_default_values() + members = ['instance_id1', 'instance_id2'] + values['members'] = members + db.instance_group_update(self.context, id, values) + result = db.instance_group_get(self.context, id) + self._assertEqualListsOfPrimitivesAsSets(result['members'], members) + # update update policies + values = self._get_default_values() + policies = ['policy1', 'policy2'] + values['policies'] = policies + db.instance_group_update(self.context, id, values) + result = db.instance_group_get(self.context, id) + self._assertEqualListsOfPrimitivesAsSets(result['policies'], policies) + # test invalid ID + self.assertRaises(exception.InstanceGroupNotFound, + db.instance_group_update, self.context, + 'invalid_id', values) + + +class InstanceGroupMetadataDBApiTestCase(InstanceGroupDBApiTestCase): + def test_instance_group_metadata_on_create(self): + values = self._get_default_values() + values['uuid'] = 'fake_id' + metadata = {'key11': 'value1', + 'key12': 'value2'} + result = self._create_instance_group(self.context, values, + metadata=metadata) + ignored_keys = ['id', 'deleted', 'deleted_at', 'updated_at', + 'created_at'] + self._assertEqualObjects(result, values, ignored_keys) + self._assertEqualObjects(metadata, result['metadetails']) + + def test_instance_group_metadata_add(self): + values = self._get_default_values() + values['uuid'] = 'fake_id' + result = self._create_instance_group(self.context, values) + id = result['uuid'] + metadata = db.instance_group_metadata_get(self.context, id) + self._assertEqualObjects(metadata, {}) + metadata = {'key1': 'value1', + 'key2': 'value2'} + db.instance_group_metadata_add(self.context, id, metadata) + metadata2 = db.instance_group_metadata_get(self.context, id) + self._assertEqualObjects(metadata, metadata2) + + def test_instance_group_update(self): + values = self._get_default_values() + values['uuid'] = 'fake_id' + result = self._create_instance_group(self.context, values) + id = result['uuid'] + metadata = {'key1': 'value1', + 'key2': 'value2'} + db.instance_group_metadata_add(self.context, id, metadata) + metadata2 = db.instance_group_metadata_get(self.context, id) + self._assertEqualObjects(metadata, metadata2) + # check add with existing keys + metadata = {'key1': 'value1', + 'key2': 'value2', + 'key3': 'value3'} + db.instance_group_metadata_add(self.context, id, metadata) + metadata3 = db.instance_group_metadata_get(self.context, id) + self._assertEqualObjects(metadata, metadata3) + + def test_instance_group_delete(self): + values = self._get_default_values() + values['uuid'] = 'fake_id' + result = self._create_instance_group(self.context, values) + id = result['uuid'] + metadata = {'key1': 'value1', + 'key2': 'value2', + 'key3': 'value3'} + db.instance_group_metadata_add(self.context, id, metadata) + metadata3 = db.instance_group_metadata_get(self.context, id) + self._assertEqualObjects(metadata, metadata3) + db.instance_group_metadata_delete(self.context, id, 'key1') + metadata = db.instance_group_metadata_get(self.context, id) + self.assertTrue('key1' not in metadata) + db.instance_group_metadata_delete(self.context, id, 'key2') + metadata = db.instance_group_metadata_get(self.context, id) + self.assertTrue('key2' not in metadata) + + def test_instance_group_metadata_invalid_ids(self): + values = self._get_default_values() + result = self._create_instance_group(self.context, values) + id = result['uuid'] + self.assertRaises(exception.InstanceGroupNotFound, + db.instance_group_metadata_get, + self.context, 'invalid') + self.assertRaises(exception.InstanceGroupNotFound, + db.instance_group_metadata_delete, self.context, + 'invalidid', 'key1') + metadata = {'key1': 'value1', + 'key2': 'value2'} + db.instance_group_metadata_add(self.context, id, metadata) + self.assertRaises(exception.InstanceGroupMetadataNotFound, + db.instance_group_metadata_delete, + self.context, id, 'invalidkey') + + +class InstanceGroupMembersDBApiTestCase(InstanceGroupDBApiTestCase): + def test_instance_group_members_on_create(self): + values = self._get_default_values() + values['uuid'] = 'fake_id' + members = ['instance_id1', 'instance_id2'] + result = self._create_instance_group(self.context, values, + members=members) + ignored_keys = ['id', 'deleted', 'deleted_at', 'updated_at', + 'created_at'] + self._assertEqualObjects(result, values, ignored_keys) + self._assertEqualListsOfPrimitivesAsSets(result['members'], members) + + def test_instance_group_members_add(self): + values = self._get_default_values() + values['uuid'] = 'fake_id' + result = self._create_instance_group(self.context, values) + id = result['uuid'] + members = db.instance_group_members_get(self.context, id) + self.assertEquals(members, []) + members2 = ['instance_id1', 'instance_id2'] + db.instance_group_members_add(self.context, id, members2) + members = db.instance_group_members_get(self.context, id) + self._assertEqualListsOfPrimitivesAsSets(members, members2) + + def test_instance_group_members_update(self): + values = self._get_default_values() + values['uuid'] = 'fake_id' + result = self._create_instance_group(self.context, values) + id = result['uuid'] + members2 = ['instance_id1', 'instance_id2'] + db.instance_group_members_add(self.context, id, members2) + members = db.instance_group_members_get(self.context, id) + self._assertEqualListsOfPrimitivesAsSets(members, members2) + # check add with existing keys + members3 = ['instance_id1', 'instance_id2', 'instance_id3'] + db.instance_group_members_add(self.context, id, members3) + members = db.instance_group_members_get(self.context, id) + self._assertEqualListsOfPrimitivesAsSets(members, members3) + + def test_instance_group_members_delete(self): + values = self._get_default_values() + values['uuid'] = 'fake_id' + result = self._create_instance_group(self.context, values) + id = result['uuid'] + members3 = ['instance_id1', 'instance_id2', 'instance_id3'] + db.instance_group_members_add(self.context, id, members3) + members = db.instance_group_members_get(self.context, id) + self._assertEqualListsOfPrimitivesAsSets(members, members3) + for instance_id in members3[:]: + db.instance_group_member_delete(self.context, id, instance_id) + members3.remove(instance_id) + members = db.instance_group_members_get(self.context, id) + self._assertEqualListsOfPrimitivesAsSets(members, members3) + + def test_instance_group_members_invalid_ids(self): + values = self._get_default_values() + result = self._create_instance_group(self.context, values) + id = result['uuid'] + self.assertRaises(exception.InstanceGroupNotFound, + db.instance_group_members_get, + self.context, 'invalid') + self.assertRaises(exception.InstanceGroupNotFound, + db.instance_group_member_delete, self.context, + 'invalidid', 'instance_id1') + members = ['instance_id1', 'instance_id2'] + db.instance_group_members_add(self.context, id, members) + self.assertRaises(exception.InstanceGroupMemberNotFound, + db.instance_group_member_delete, + self.context, id, 'invalid_id') + + +class InstanceGroupPoliciesDBApiTestCase(InstanceGroupDBApiTestCase): + def test_instance_group_policies_on_create(self): + values = self._get_default_values() + values['uuid'] = 'fake_id' + policies = ['policy1', 'policy2'] + result = self._create_instance_group(self.context, values, + policies=policies) + ignored_keys = ['id', 'deleted', 'deleted_at', 'updated_at', + 'created_at'] + self._assertEqualObjects(result, values, ignored_keys) + self._assertEqualListsOfPrimitivesAsSets(result['policies'], policies) + + def test_instance_group_policies_add(self): + values = self._get_default_values() + values['uuid'] = 'fake_id' + result = self._create_instance_group(self.context, values) + id = result['uuid'] + policies = db.instance_group_policies_get(self.context, id) + self.assertEquals(policies, []) + policies2 = ['policy1', 'policy2'] + db.instance_group_policies_add(self.context, id, policies2) + policies = db.instance_group_policies_get(self.context, id) + self._assertEqualListsOfPrimitivesAsSets(policies, policies2) + + def test_instance_group_policies_update(self): + values = self._get_default_values() + values['uuid'] = 'fake_id' + result = self._create_instance_group(self.context, values) + id = result['uuid'] + policies2 = ['policy1', 'policy2'] + db.instance_group_policies_add(self.context, id, policies2) + policies = db.instance_group_policies_get(self.context, id) + self._assertEqualListsOfPrimitivesAsSets(policies, policies2) + policies3 = ['policy1', 'policy2', 'policy3'] + db.instance_group_policies_add(self.context, id, policies3) + policies = db.instance_group_policies_get(self.context, id) + self._assertEqualListsOfPrimitivesAsSets(policies, policies3) + + def test_instance_group_policies_delete(self): + values = self._get_default_values() + values['uuid'] = 'fake_id' + result = self._create_instance_group(self.context, values) + id = result['uuid'] + policies3 = ['policy1', 'policy2', 'policy3'] + db.instance_group_policies_add(self.context, id, policies3) + policies = db.instance_group_policies_get(self.context, id) + self._assertEqualListsOfPrimitivesAsSets(policies, policies3) + for policy in policies3[:]: + db.instance_group_policy_delete(self.context, id, policy) + policies3.remove(policy) + policies = db.instance_group_policies_get(self.context, id) + self._assertEqualListsOfPrimitivesAsSets(policies, policies3) + + def test_instance_group_policies_invalid_ids(self): + values = self._get_default_values() + result = self._create_instance_group(self.context, values) + id = result['uuid'] + self.assertRaises(exception.InstanceGroupNotFound, + db.instance_group_policies_get, + self.context, 'invalid') + self.assertRaises(exception.InstanceGroupNotFound, + db.instance_group_policy_delete, self.context, + 'invalidid', 'policy1') + policies = ['policy1', 'policy2'] + db.instance_group_policies_add(self.context, id, policies) + self.assertRaises(exception.InstanceGroupPolicyNotFound, + db.instance_group_policy_delete, + self.context, id, 'invalid_policy') diff --git a/nova/tests/db/test_migrations.py b/nova/tests/db/test_migrations.py index 0e89cd521..c73093d7d 100644 --- a/nova/tests/db/test_migrations.py +++ b/nova/tests/db/test_migrations.py @@ -62,6 +62,7 @@ import nova.db.sqlalchemy.migrate_repo from nova.db.sqlalchemy import utils as db_utils from nova.openstack.common import log as logging from nova.openstack.common import timeutils +from nova.openstack.common import uuidutils from nova import test from nova import utils import nova.virt.baremetal.db.sqlalchemy.migrate_repo @@ -1582,6 +1583,49 @@ class TestNovaMigrations(BaseMigrationTestCase, CommonTestsMixIn): self.assertEqual(bdm_3s[3].image_id, 'fake_image_2') self.assertEqual(bdm_3s[3].boot_index, 0) + # addition of the vm instance groups + def _check_no_group_instance_tables(self, engine): + self.assertRaises(sqlalchemy.exc.NoSuchTableError, + db_utils.get_table, engine, + 'instance_groups') + self.assertRaises(sqlalchemy.exc.NoSuchTableError, + db_utils.get_table, engine, + 'instance_group_member') + self.assertRaises(sqlalchemy.exc.NoSuchTableError, + db_utils.get_table, engine, + 'instance_group_policy') + self.assertRaises(sqlalchemy.exc.NoSuchTableError, + db_utils.get_table, engine, + 'instance_group_metadata') + + def _check_group_instance_groups(self, engine): + groups = db_utils.get_table(engine, 'instance_groups') + uuid4 = uuidutils.generate_uuid() + uuid5 = uuidutils.generate_uuid() + group_data = [ + {'id': 4, 'deleted': 4, 'uuid': uuid4}, + {'id': 5, 'deleted': 0, 'uuid': uuid5}, + ] + engine.execute(groups.insert(), group_data) + group = groups.select(groups.c.id == 4).execute().first() + self.assertEqual(4, group.deleted) + group = groups.select(groups.c.id == 5).execute().first() + self.assertEqual(0, group.deleted) + + def _pre_upgrade_187(self, engine): + self._check_no_group_instance_tables(engine) + + def _check_187(self, engine, data): + self._check_group_instance_groups(engine) + tables = ['instance_group_policy', 'instance_group_metadata', + 'instance_group_member'] + for table in tables: + db_utils.get_table(engine, table) + + def _post_downgrade_187(self, engine): + # check that groups does not exist + self._check_no_group_instance_tables(engine) + class TestBaremetalMigrations(BaseMigrationTestCase, CommonTestsMixIn): """Test sqlalchemy-migrate migrations.""" -- cgit