Quotas fixed.
quotas unittests fixed.
This commit is contained in:
parent
06d200f0aa
commit
68c71d8de2
manila
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user