summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2013-06-17 23:16:55 +0000
committerGerrit Code Review <review@openstack.org>2013-06-17 23:16:55 +0000
commitf27a4f306183017da5dfceeb7d1dfa8cf4cbb433 (patch)
tree8d5d2cbe7472694c902b3cb6d9c9e9725e37197d
parent5d26786ff3c37991bf79a2d91e709277c194bee6 (diff)
parent7fdba82bad3c4e550bda4db03ed9d1ab7ab62934 (diff)
downloadnova-f27a4f306183017da5dfceeb7d1dfa8cf4cbb433.tar.gz
nova-f27a4f306183017da5dfceeb7d1dfa8cf4cbb433.tar.xz
nova-f27a4f306183017da5dfceeb7d1dfa8cf4cbb433.zip
Merge "Add unique constraints to Cell."
-rw-r--r--nova/db/sqlalchemy/api.py5
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/189_add_cells_uc.py40
-rw-r--r--nova/db/sqlalchemy/models.py4
-rw-r--r--nova/exception.py4
-rw-r--r--nova/tests/db/test_db_api.py5
-rw-r--r--nova/tests/db/test_migrations.py29
6 files changed, 86 insertions, 1 deletions
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 664425441..55ef03006 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -4048,7 +4048,10 @@ def instance_type_extra_specs_update_or_create(context, flavor_id, specs):
def cell_create(context, values):
cell = models.Cell()
cell.update(values)
- cell.save()
+ try:
+ cell.save()
+ except db_exc.DBDuplicateEntry:
+ raise exception.CellExists(name=values['name'])
return cell
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/189_add_cells_uc.py b/nova/db/sqlalchemy/migrate_repo/versions/189_add_cells_uc.py
new file mode 100644
index 000000000..d0606e9f9
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/189_add_cells_uc.py
@@ -0,0 +1,40 @@
+# 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
+
+from nova.db.sqlalchemy import utils
+
+
+UC_NAME = 'uniq_cell_name0deleted'
+COLUMNS = ('name', 'deleted')
+TABLE_NAME = 'cells'
+
+
+def upgrade(migrate_engine):
+ meta = MetaData(bind=migrate_engine)
+ t = Table(TABLE_NAME, meta, autoload=True)
+
+ utils.drop_old_duplicate_entries_from_table(migrate_engine, TABLE_NAME,
+ True, *COLUMNS)
+ 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)
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 815041638..9b6d849d3 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -859,6 +859,10 @@ class Cell(BASE, NovaBase):
of entries with is_parent=True or False
"""
__tablename__ = 'cells'
+ __table_args__ = (schema.UniqueConstraint(
+ "name", "deleted", name="uniq_cell_name0deleted"
+ ),
+ )
id = Column(Integer, primary_key=True)
# Name here is the 'short name' of a cell. For instance: 'child1'
name = Column(String(255))
diff --git a/nova/exception.py b/nova/exception.py
index 68cf1f991..893c0df75 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -825,6 +825,10 @@ class CellNotFound(NotFound):
message = _("Cell %(cell_name)s doesn't exist.")
+class CellExists(Duplicate):
+ message = _("Cell with name %(name)s already exists.")
+
+
class CellRoutingInconsistency(NovaException):
message = _("Inconsistency in cell routing: %(reason)s")
diff --git a/nova/tests/db/test_db_api.py b/nova/tests/db/test_db_api.py
index 7f68dfbc4..0a5b75bf4 100644
--- a/nova/tests/db/test_db_api.py
+++ b/nova/tests/db/test_db_api.py
@@ -4967,6 +4967,11 @@ class CellTestCase(test.TestCase, ModelsObjectComparatorMixin):
self.assertRaises(exception.CellNotFound, db.cell_update, self.ctxt,
'cellnotinbase', self._get_cell_base_values())
+ def test_cell_create_exists(self):
+ db.cell_create(self.ctxt, self._get_cell_base_values())
+ self.assertRaises(exception.CellExists, db.cell_create,
+ self.ctxt, self._get_cell_base_values())
+
class ArchiveTestCase(test.TestCase):
diff --git a/nova/tests/db/test_migrations.py b/nova/tests/db/test_migrations.py
index dc77a27d7..812f0d8ae 100644
--- a/nova/tests/db/test_migrations.py
+++ b/nova/tests/db/test_migrations.py
@@ -1643,6 +1643,35 @@ class TestNovaMigrations(BaseMigrationTestCase, CommonTestsMixIn):
rows = services.select().execute().fetchall()
self.assertFalse('disabled_reason' in rows[0])
+ def _pre_upgrade_189(self, engine):
+ cells = db_utils.get_table(engine, 'cells')
+ data = [
+ {'name': 'name_123', 'deleted': 0},
+ {'name': 'name_123', 'deleted': 0},
+ {'name': 'name_345', 'deleted': 0},
+ ]
+ for item in data:
+ cells.insert().values(item).execute()
+ return data
+
+ def _check_189(self, engine, data):
+ cells = db_utils.get_table(engine, 'cells')
+
+ def get_(name, deleted):
+ deleted_value = 0 if not deleted else cells.c.id
+ return cells.select().\
+ where(cells.c.name == name).\
+ where(cells.c.deleted == deleted_value).\
+ execute().\
+ fetchall()
+
+ self.assertEqual(1, len(get_('name_123', False)))
+ self.assertEqual(1, len(get_('name_123', True)))
+ self.assertEqual(1, len(get_('name_345', False)))
+ self.assertRaises(sqlalchemy.exc.IntegrityError,
+ cells.insert().execute,
+ {'name': 'name_123', 'deleted': 0})
+
class TestBaremetalMigrations(BaseMigrationTestCase, CommonTestsMixIn):
"""Test sqlalchemy-migrate migrations."""