Quotas fixed.

quotas unittests fixed.
This commit is contained in:
Yulia Portnova 2013-09-06 15:37:58 +03:00
parent 06d200f0aa
commit 68c71d8de2
8 changed files with 1430 additions and 1391 deletions

@ -309,6 +309,11 @@ def share_create(context, values):
return IMPL.share_create(context, values)
def share_data_get_for_project(context, project_id, session=None):
"""Get (share_count, gigabytes) for project."""
return IMPL.share_data_get_for_project(context, project_id)
def share_update(context, share_id, values):
"""Update share fields."""
return IMPL.share_update(context, share_id, values)
@ -375,6 +380,11 @@ def share_snapshot_create(context, values):
return IMPL.share_snapshot_create(context, values)
def snapshot_data_get_for_project(context, project_id, session=None):
"""Get (snapshot_count, gigabytes) for project."""
return IMPL.snapshot_data_get_for_project(context, project_id)
def share_snapshot_destroy(context, snapshot_id):
"""Destroy the snapshot or raise if it does not exist."""
return IMPL.share_snapshot_destroy(context, snapshot_id)

@ -827,6 +827,20 @@ def share_create(context, values):
return share_ref
@require_admin_context
def share_data_get_for_project(context, project_id, session=None):
query = model_query(context,
func.count(models.Share.id),
func.sum(models.Share.size),
read_deleted="no",
session=session).\
filter_by(project_id=project_id)
result = query.first()
return (result[0] or 0, result[1] or 0)
@require_context
def share_update(context, share_id, values):
session = get_session()
@ -957,6 +971,23 @@ def share_snapshot_create(context, values):
return share_snapshot_get(context, values['id'], session=session)
@require_admin_context
def snapshot_data_get_for_project(context, project_id, session=None):
# TODO: Add
raise NotImplementedError()
query = model_query(context,
func.count(models.ShareSnapshot.id),
func.sum(models.ShareSnapshot.share.size),
read_deleted="no",
session=session).\
filter_by(project_id=project_id)
result = query.first()
return (result[0] or 0, result[1] or 0)
@require_admin_context
def share_snapshot_destroy(context, snapshot_id):
session = get_session()

@ -335,6 +335,23 @@ class QuotaError(ManilaException):
safe = True
class ShareSizeExceedsAvailableQuota(QuotaError):
message = _("Requested share or snapshot exceeds "
"allowed Gigabytes quota")
class ShareSizeExceedsQuota(QuotaError):
message = _("Maximum share/snapshot size exceeded")
class ShareLimitExceeded(QuotaError):
message = _("Maximum number of shares allowed (%(allowed)d) exceeded")
class SnapshotLimitExceeded(QuotaError):
message = _("Maximum number of snapshots allowed (%(allowed)d) exceeded")
class Duplicate3PARHost(ManilaException):
message = _("3PAR Host already exists: %(err)s. %(info)s")
@ -443,3 +460,6 @@ class InvalidShareSnapshot(ManilaException):
class SwiftConnectionFailed(ManilaException):
message = _("Connection to swift failed") + ": %(reason)s"

@ -32,15 +32,15 @@ from manila.openstack.common import timeutils
LOG = logging.getLogger(__name__)
quota_opts = [
cfg.IntOpt('quota_volumes',
cfg.IntOpt('quota_shares',
default=10,
help='number of volumes allowed per project'),
help='number of shares allowed per project'),
cfg.IntOpt('quota_snapshots',
default=10,
help='number of volume snapshots allowed per project'),
help='number of share snapshots allowed per project'),
cfg.IntOpt('quota_gigabytes',
default=1000,
help='number of volume gigabytes (snapshots are also included) '
help='number of share gigabytes (snapshots are also included) '
'allowed per project'),
cfg.IntOpt('reservation_expire',
default=86400,
@ -386,7 +386,7 @@ class BaseResource(object):
"""
Initializes a Resource.
:param name: The name of the resource, i.e., "volumes".
:param name: The name of the resource, i.e., "shares".
:param flag: The name of the flag or configuration option
which specifies the default value of the quota
for this resource.
@ -457,7 +457,7 @@ class ReservableResource(BaseResource):
Initializes a ReservableResource.
Reservable resources are those resources which directly
correspond to objects in the database, i.e., volumes, gigabytes,
correspond to objects in the database, i.e., shares, gigabytes,
etc. A ReservableResource must be constructed with a usage
synchronization function, which will be called to determine the
current counts of one or more resources.
@ -472,7 +472,7 @@ class ReservableResource(BaseResource):
synchronization functions may be associated with more than one
ReservableResource.
:param name: The name of the resource, i.e., "volumes".
:param name: The name of the resource, i.e., "shares".
:param sync: A callable which returns a dictionary to
resynchronize the in_use count for one or more
resources, as described above.
@ -502,7 +502,7 @@ class CountableResource(AbsoluteResource):
Initializes a CountableResource.
Countable resources are those resources which directly
correspond to objects in the database, i.e., volumes, gigabytes,
correspond to objects in the database, i.e., shares, gigabytes,
etc., but for which a count by project ID is inappropriate. A
CountableResource must be constructed with a counting
function, which will be called to determine the current counts
@ -518,7 +518,7 @@ class CountableResource(AbsoluteResource):
required functionality, until a better approach to solving
this problem can be evolved.
:param name: The name of the resource, i.e., "volumes".
:param name: The name of the resource, i.e., "shares".
:param count: A callable which returns the count of the
resource. The arguments passed are as described
above.
@ -774,10 +774,10 @@ class QuotaEngine(object):
return sorted(self._resources.keys())
def _sync_volumes(context, project_id, session):
(volumes, gigs) = db.volume_data_get_for_project(context,
project_id,
session=session)
def _sync_shares(context, project_id, session):
(volumes, gigs) = db.share_data_get_for_project(context,
project_id,
session=session)
return {'volumes': volumes}
@ -789,24 +789,27 @@ def _sync_snapshots(context, project_id, session):
def _sync_gigabytes(context, project_id, session):
(_junk, vol_gigs) = db.volume_data_get_for_project(context,
project_id,
session=session)
(_junk, share_gigs) = db.share_data_get_for_project(context,
project_id,
session=session)
if FLAGS.no_snapshot_gb_quota:
return {'gigabytes': vol_gigs}
return {'gigabytes': share_gigs}
(_junk, snap_gigs) = db.snapshot_data_get_for_project(context,
project_id,
session=session)
return {'gigabytes': vol_gigs + snap_gigs}
# TODO: Uncomment when Snapshot size is implemented
# (_junk, snap_gigs) = db.snapshot_data_get_for_project(context,
# project_id,
# session=session)
# return {'gigabytes': share_gigs + snap_gigs}
return {'gigabytes': share_gigs}
QUOTAS = QuotaEngine()
resources = [
ReservableResource('volumes', _sync_volumes, 'quota_volumes'),
ReservableResource('snapshots', _sync_snapshots, 'quota_snapshots'),
ReservableResource('shares', _sync_shares, 'quota_shares'),
# TODO: Uncomment when Snapshot size is implemented
# ReservableResource('snapshots', _sync_snapshots, 'quota_snapshots'),
ReservableResource('gigabytes', _sync_gigabytes, 'quota_gigabytes'), ]

@ -41,6 +41,7 @@ FLAGS = flags.FLAGS
LOG = logging.getLogger(__name__)
GB = 1048576 * 1024
QUOTAS = quota.QUOTAS
def wrap_check_policy(func):
@ -110,6 +111,33 @@ class API(base.Base):
msg = (_("Invalid share type provided: %s") % share_proto)
raise exception.InvalidInput(reason=msg)
try:
reservations = QUOTAS.reserve(context, shares=1, gigabytes=size)
except exception.OverQuota as e:
overs = e.kwargs['overs']
usages = e.kwargs['usages']
quotas = e.kwargs['quotas']
def _consumed(name):
return (usages[name]['reserved'] + usages[name]['in_use'])
if 'gigabytes' in overs:
msg = _("Quota exceeded for %(s_pid)s, tried to create "
"%(s_size)sG volume (%(d_consumed)dG of %(d_quota)dG "
"already consumed)")
LOG.warn(msg % {'s_pid': context.project_id,
's_size': size,
'd_consumed': _consumed('gigabytes'),
'd_quota': quotas['gigabytes']})
raise exception.ShareSizeExceedsAvailableQuota()
elif 'volumes' in overs:
msg = _("Quota exceeded for %(s_pid)s, tried to create "
"volume (%(d_consumed)d volumes "
"already consumed)")
LOG.warn(msg % {'s_pid': context.project_id,
'd_consumed': _consumed('volumes')})
raise exception.ShareLimitExceeded(allowed=quotas['shares'])
if availability_zone is None:
availability_zone = FLAGS.storage_availability_zone

@ -33,3 +33,4 @@ def set_defaults(conf):
conf.set_default('sql_connection', "sqlite://")
conf.set_default('sqlite_synchronous', False)
conf.set_default('policy_file', 'manila/tests/policy.json')
conf.set_default('share_export_ip', '0.0.0.0')

File diff suppressed because it is too large Load Diff

@ -30,6 +30,7 @@ from manila.share import api as share_api
from manila.share import rpcapi as share_rpcapi
from manila import test
from manila.tests.db import fakes as db_fakes
from manila import quota
def fake_share(id, **kwargs):
@ -104,6 +105,7 @@ class ShareAPITestCase(test.TestCase):
self.stubs.Set(self.api, 'scheduler_rpcapi', self.scheduler_rpcapi)
self.stubs.Set(self.api, 'share_rpcapi', self.share_rpcapi)
self.stubs.Set(quota.QUOTAS, 'reserve', lambda *args, **kwargs : None)
def tearDown(self):
super(ShareAPITestCase, self).tearDown()