Add unique constraints to Quota.
Added unique constraint 'uniq_quotas0project_id0resource0deleted'
('project_id', 'resource', 'deleted') to Quota model and migrate sripts.
Added new exception `QuotaExists`. Updated quota_create(). Change
quotas duplicates control in create/update code to new DB UC exception.
Tests updated respectively.
blueprint db-enforce-unique-keys
Change-Id: Ia615a1c1a7dc75bd19831fcd0acfc9b78c1b3f6f
This commit is contained in:
@@ -162,9 +162,9 @@ class QuotaSetsController(object):
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
try:
|
||||
db.quota_update(context, project_id, key, value)
|
||||
except exception.ProjectQuotaNotFound:
|
||||
db.quota_create(context, project_id, key, value)
|
||||
except exception.QuotaExists:
|
||||
db.quota_update(context, project_id, key, value)
|
||||
except exception.AdminRequired:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
return {'quota_set': self._get_quotas(context, id)}
|
||||
|
||||
@@ -234,9 +234,9 @@ class ProjectCommands(object):
|
||||
if value.lower() == 'unlimited':
|
||||
value = -1
|
||||
try:
|
||||
db.quota_update(ctxt, project_id, key, value)
|
||||
except exception.ProjectQuotaNotFound:
|
||||
db.quota_create(ctxt, project_id, key, value)
|
||||
except exception.QuotaExists:
|
||||
db.quota_update(ctxt, project_id, key, value)
|
||||
else:
|
||||
print _('%(key)s is not a valid quota key. Valid options are: '
|
||||
'%(options)s.') % {'key': key,
|
||||
|
||||
@@ -2586,7 +2586,10 @@ def quota_create(context, project_id, resource, limit):
|
||||
quota_ref.project_id = project_id
|
||||
quota_ref.resource = resource
|
||||
quota_ref.hard_limit = limit
|
||||
try:
|
||||
quota_ref.save()
|
||||
except db_exc.DBDuplicateEntry:
|
||||
raise exception.QuotaExists(project_id=project_id, resource=resource)
|
||||
return quota_ref
|
||||
|
||||
|
||||
|
||||
40
nova/db/sqlalchemy/migrate_repo/versions/191_add_quota_uc.py
Normal file
40
nova/db/sqlalchemy/migrate_repo/versions/191_add_quota_uc.py
Normal file
@@ -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_quotas0project_id0resource0deleted'
|
||||
COLUMNS = ('project_id', 'resource', 'deleted')
|
||||
TABLE_NAME = 'quotas'
|
||||
|
||||
|
||||
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)
|
||||
@@ -367,7 +367,11 @@ class Quota(BASE, NovaBase):
|
||||
"""
|
||||
|
||||
__tablename__ = 'quotas'
|
||||
__table_args__ = ()
|
||||
__table_args__ = (
|
||||
schema.UniqueConstraint("project_id", "resource", "deleted",
|
||||
name="uniq_quotas0project_id0resource0deleted"
|
||||
),
|
||||
)
|
||||
id = Column(Integer, primary_key=True)
|
||||
|
||||
project_id = Column(String(255), nullable=True)
|
||||
|
||||
@@ -704,6 +704,11 @@ class QuotaNotFound(NotFound):
|
||||
message = _("Quota could not be found")
|
||||
|
||||
|
||||
class QuotaExists(Duplicate):
|
||||
message = _("Quota exists for project %(project_id)s, "
|
||||
"resource %(resource)s")
|
||||
|
||||
|
||||
class QuotaResourceUnknown(QuotaNotFound):
|
||||
message = _("Unknown quota resources %(unknown)s.")
|
||||
|
||||
|
||||
@@ -4753,6 +4753,11 @@ class QuotaTestCase(test.TestCase, ModelsObjectComparatorMixin):
|
||||
for key, value in expected.iteritems():
|
||||
self.assertEqual(value, quota_usage[key])
|
||||
|
||||
def test_quota_create_exists(self):
|
||||
db.quota_create(self.ctxt, 'project1', 'resource1', 41)
|
||||
self.assertRaises(exception.QuotaExists, db.quota_create, self.ctxt,
|
||||
'project1', 'resource1', 42)
|
||||
|
||||
|
||||
class QuotaClassTestCase(test.TestCase, ModelsObjectComparatorMixin):
|
||||
|
||||
|
||||
@@ -1699,6 +1699,36 @@ class TestNovaMigrations(BaseMigrationTestCase, CommonTestsMixIn):
|
||||
security_groups.insert().execute,
|
||||
dict(name='group2', project_id='fake', deleted=0))
|
||||
|
||||
def _pre_upgrade_191(self, engine):
|
||||
quotas = db_utils.get_table(engine, 'quotas')
|
||||
data = [
|
||||
{'project_id': 'project1', 'resource': 'resource1', 'deleted': 0},
|
||||
{'project_id': 'project1', 'resource': 'resource1', 'deleted': 0},
|
||||
{'project_id': 'project2', 'resource': 'resource1', 'deleted': 0},
|
||||
]
|
||||
for item in data:
|
||||
quotas.insert().values(item).execute()
|
||||
return data
|
||||
|
||||
def _check_191(self, engine, data):
|
||||
quotas = db_utils.get_table(engine, 'quotas')
|
||||
|
||||
def get_(project_id, deleted):
|
||||
deleted_value = 0 if not deleted else quotas.c.id
|
||||
return quotas.select().\
|
||||
where(quotas.c.project_id == project_id).\
|
||||
where(quotas.c.deleted == deleted_value).\
|
||||
execute().\
|
||||
fetchall()
|
||||
|
||||
self.assertEqual(1, len(get_('project1', False)))
|
||||
self.assertEqual(1, len(get_('project1', True)))
|
||||
self.assertEqual(1, len(get_('project2', False)))
|
||||
self.assertRaises(sqlalchemy.exc.IntegrityError,
|
||||
quotas.insert().execute,
|
||||
{'project_id': 'project1', 'resource': 'resource1',
|
||||
'deleted': 0})
|
||||
|
||||
|
||||
class TestBaremetalMigrations(BaseMigrationTestCase, CommonTestsMixIn):
|
||||
"""Test sqlalchemy-migrate migrations."""
|
||||
|
||||
Reference in New Issue
Block a user