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)
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.quota_update(context, project_id, key, value)
|
|
||||||
except exception.ProjectQuotaNotFound:
|
|
||||||
db.quota_create(context, project_id, key, value)
|
db.quota_create(context, project_id, key, value)
|
||||||
|
except exception.QuotaExists:
|
||||||
|
db.quota_update(context, project_id, key, value)
|
||||||
except exception.AdminRequired:
|
except exception.AdminRequired:
|
||||||
raise webob.exc.HTTPForbidden()
|
raise webob.exc.HTTPForbidden()
|
||||||
return {'quota_set': self._get_quotas(context, id)}
|
return {'quota_set': self._get_quotas(context, id)}
|
||||||
|
|||||||
@@ -234,9 +234,9 @@ class ProjectCommands(object):
|
|||||||
if value.lower() == 'unlimited':
|
if value.lower() == 'unlimited':
|
||||||
value = -1
|
value = -1
|
||||||
try:
|
try:
|
||||||
db.quota_update(ctxt, project_id, key, value)
|
|
||||||
except exception.ProjectQuotaNotFound:
|
|
||||||
db.quota_create(ctxt, project_id, key, value)
|
db.quota_create(ctxt, project_id, key, value)
|
||||||
|
except exception.QuotaExists:
|
||||||
|
db.quota_update(ctxt, project_id, key, value)
|
||||||
else:
|
else:
|
||||||
print _('%(key)s is not a valid quota key. Valid options are: '
|
print _('%(key)s is not a valid quota key. Valid options are: '
|
||||||
'%(options)s.') % {'key': key,
|
'%(options)s.') % {'key': key,
|
||||||
|
|||||||
@@ -2586,7 +2586,10 @@ def quota_create(context, project_id, resource, limit):
|
|||||||
quota_ref.project_id = project_id
|
quota_ref.project_id = project_id
|
||||||
quota_ref.resource = resource
|
quota_ref.resource = resource
|
||||||
quota_ref.hard_limit = limit
|
quota_ref.hard_limit = limit
|
||||||
quota_ref.save()
|
try:
|
||||||
|
quota_ref.save()
|
||||||
|
except db_exc.DBDuplicateEntry:
|
||||||
|
raise exception.QuotaExists(project_id=project_id, resource=resource)
|
||||||
return quota_ref
|
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'
|
__tablename__ = 'quotas'
|
||||||
__table_args__ = ()
|
__table_args__ = (
|
||||||
|
schema.UniqueConstraint("project_id", "resource", "deleted",
|
||||||
|
name="uniq_quotas0project_id0resource0deleted"
|
||||||
|
),
|
||||||
|
)
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
|
|
||||||
project_id = Column(String(255), nullable=True)
|
project_id = Column(String(255), nullable=True)
|
||||||
|
|||||||
@@ -704,6 +704,11 @@ class QuotaNotFound(NotFound):
|
|||||||
message = _("Quota could not be found")
|
message = _("Quota could not be found")
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaExists(Duplicate):
|
||||||
|
message = _("Quota exists for project %(project_id)s, "
|
||||||
|
"resource %(resource)s")
|
||||||
|
|
||||||
|
|
||||||
class QuotaResourceUnknown(QuotaNotFound):
|
class QuotaResourceUnknown(QuotaNotFound):
|
||||||
message = _("Unknown quota resources %(unknown)s.")
|
message = _("Unknown quota resources %(unknown)s.")
|
||||||
|
|
||||||
|
|||||||
@@ -4753,6 +4753,11 @@ class QuotaTestCase(test.TestCase, ModelsObjectComparatorMixin):
|
|||||||
for key, value in expected.iteritems():
|
for key, value in expected.iteritems():
|
||||||
self.assertEqual(value, quota_usage[key])
|
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):
|
class QuotaClassTestCase(test.TestCase, ModelsObjectComparatorMixin):
|
||||||
|
|
||||||
|
|||||||
@@ -1699,6 +1699,36 @@ class TestNovaMigrations(BaseMigrationTestCase, CommonTestsMixIn):
|
|||||||
security_groups.insert().execute,
|
security_groups.insert().execute,
|
||||||
dict(name='group2', project_id='fake', deleted=0))
|
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):
|
class TestBaremetalMigrations(BaseMigrationTestCase, CommonTestsMixIn):
|
||||||
"""Test sqlalchemy-migrate migrations."""
|
"""Test sqlalchemy-migrate migrations."""
|
||||||
|
|||||||
Reference in New Issue
Block a user