diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 638768367e..2564e2251c 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1303,7 +1303,7 @@ def virtual_interface_create(context, values): @require_context def _virtual_interface_query(context, session=None): return model_query(context, models.VirtualInterface, session=session, - read_deleted="yes") + read_deleted="no") @require_context @@ -1380,7 +1380,7 @@ def virtual_interface_delete_by_instance(context, instance_uuid): """ _virtual_interface_query(context).\ filter_by(instance_uuid=instance_uuid).\ - delete() + soft_delete() @require_context diff --git a/nova/db/sqlalchemy/migrate_repo/versions/192_change_virtual_interface_uc.py b/nova/db/sqlalchemy/migrate_repo/versions/192_change_virtual_interface_uc.py new file mode 100644 index 0000000000..dfcdc47d33 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/192_change_virtual_interface_uc.py @@ -0,0 +1,53 @@ +# Copyright 2013 Mirantis Inc. +# All Rights Reserved +# +# 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. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +from migrate.changeset import UniqueConstraint +from sqlalchemy import MetaData, Table, Index + +from nova.db.sqlalchemy import utils + + +OLD_UC_NAME = 'uniq_virtual_interfaces0address' +UC_NAME = 'uniq_virtual_interfaces0address0deleted' +OLD_COLUMN = 'address' +COLUMNS = ('address', 'deleted') +TABLE_NAME = 'virtual_interfaces' + + +def upgrade(migrate_engine): + utils.drop_unique_constraint(migrate_engine, TABLE_NAME, OLD_UC_NAME, + OLD_COLUMN) + meta = MetaData(bind=migrate_engine) + t = Table(TABLE_NAME, meta, autoload=True) + if migrate_engine.name == "mysql": + index = Index(OLD_COLUMN, t.c[OLD_COLUMN], unique=True) + index.drop() + uc = UniqueConstraint(*COLUMNS, table=t, name=UC_NAME) + uc.create() + + +def downgrade(migrate_engine): + utils.drop_unique_constraint(migrate_engine, TABLE_NAME, UC_NAME, *COLUMNS) + meta = MetaData(bind=migrate_engine) + t = Table(TABLE_NAME, meta, autoload=True) + delete_statement = t.delete().where(t.c.deleted != 0) + migrate_engine.execute(delete_statement) + uc = UniqueConstraint(OLD_COLUMN, table=t, name=OLD_UC_NAME) + uc.create() + if migrate_engine.name == "mysql": + index = Index(OLD_COLUMN, t.c[OLD_COLUMN], unique=True) + index.create() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 6da9d8e19d..690edb96a2 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -698,13 +698,13 @@ class VirtualInterface(BASE, NovaBase): """Represents a virtual interface on an instance.""" __tablename__ = 'virtual_interfaces' __table_args__ = ( - schema.UniqueConstraint("address", - name="uniq_virtual_interfaces0address"), + schema.UniqueConstraint("address", "deleted", + name="uniq_virtual_interfaces0address0deleted"), Index('network_id', 'network_id'), Index('virtual_interfaces_instance_uuid_fkey', 'instance_uuid'), ) id = Column(Integer, primary_key=True, nullable=False) - address = Column(String(255), unique=True, nullable=True) + address = Column(String(255), nullable=True) network_id = Column(Integer, nullable=True) instance_uuid = Column(String(36), ForeignKey('instances.uuid'), nullable=True) diff --git a/nova/tests/db/test_db_api.py b/nova/tests/db/test_db_api.py index 8da1bbab61..70d3363516 100644 --- a/nova/tests/db/test_db_api.py +++ b/nova/tests/db/test_db_api.py @@ -4225,11 +4225,8 @@ class VirtualInterfaceTestCase(test.TestCase, ModelsObjectComparatorMixin): 'created_at', 'uuid'] self._assertEqualObjects(vif, self._get_base_values(), ignored_keys) - @test.testtools.skip("bug 1156227") def test_virtual_interface_create_with_duplicate_address(self): vif = self._create_virt_interface({}) - # NOTE(boris-42): Due to the bug 1156227 this won't work. In havana-1 - # it will be fixed. self.assertRaises(exception.VirtualInterfaceCreateException, self._create_virt_interface, {"uuid": vif['uuid']}) diff --git a/nova/tests/db/test_migrations.py b/nova/tests/db/test_migrations.py index 9e0a791611..2f621a65fc 100644 --- a/nova/tests/db/test_migrations.py +++ b/nova/tests/db/test_migrations.py @@ -1729,6 +1729,29 @@ class TestNovaMigrations(BaseMigrationTestCase, CommonTestsMixIn): {'project_id': 'project1', 'resource': 'resource1', 'deleted': 0}) + def _check_192(self, engine, data): + virtual_if = db_utils.get_table(engine, 'virtual_interfaces') + values = {'address': 'address0', 'deleted': 0} + virtual_if.insert().values(values).execute() + values['deleted'] = 1 + virtual_if.insert().values(values).execute() + values['deleted'] = 0 + self.assertRaises(sqlalchemy.exc.IntegrityError, + virtual_if.insert().execute, + values) + + def _post_downgrade_192(self, engine): + virtual_if = db_utils.get_table(engine, 'virtual_interfaces') + deleted = virtual_if.select().\ + where(virtual_if.c.deleted != 0).\ + execute().fetchall() + self.assertEqual([], deleted) + values = {'address': 'address1'} + virtual_if.insert().values(values).execute() + self.assertRaises(sqlalchemy.exc.IntegrityError, + virtual_if.insert().execute, + values) + class TestBaremetalMigrations(BaseMigrationTestCase, CommonTestsMixIn): """Test sqlalchemy-migrate migrations."""