diff --git a/manila/db/sqlalchemy/api.py b/manila/db/sqlalchemy/api.py index 73bfc8efa0..df7f1309f3 100644 --- a/manila/db/sqlalchemy/api.py +++ b/manila/db/sqlalchemy/api.py @@ -972,19 +972,16 @@ def share_snapshot_create(context, values): @require_admin_context def snapshot_data_get_for_project(context, project_id, session=None): - # TODO(yportnova): Uncomment when snapshot size implemented - raise NotImplementedError() - # - # query = model_query(context, - # func.count(models.ShareSnapshot.id), - # func.sum(models.ShareSnapshot.size), - # read_deleted="no", - # session=session).\ - # filter_by(project_id=project_id) - # - # result = query.first() - # - # return (result[0] or 0, result[1] or 0) + query = model_query(context, + func.count(models.ShareSnapshot.id), + func.sum(models.ShareSnapshot.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 diff --git a/manila/quota.py b/manila/quota.py index 311845549c..4bfb7affe1 100644 --- a/manila/quota.py +++ b/manila/quota.py @@ -16,7 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. -"""Quotas for volumes.""" +"""Quotas for shares.""" import datetime @@ -775,10 +775,10 @@ class QuotaEngine(object): def _sync_shares(context, project_id, session): - (volumes, gigs) = db.share_data_get_for_project(context, - project_id, - session=session) - return {'volumes': volumes} + (shares, gigs) = db.share_data_get_for_project(context, + project_id, + session=session) + return {'shares': shares} def _sync_snapshots(context, project_id, session): @@ -790,17 +790,15 @@ def _sync_snapshots(context, project_id, session): def _sync_gigabytes(context, project_id, session): (_junk, share_gigs) = db.share_data_get_for_project(context, - project_id, - session=session) + project_id, + session=session) if FLAGS.no_snapshot_gb_quota: return {'gigabytes': share_gigs} - # TODO(yportnova): 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} + (_junk, snap_gigs) = db.snapshot_data_get_for_project(context, + project_id, + session=session) + return {'gigabytes': share_gigs + snap_gigs} QUOTAS = QuotaEngine() @@ -808,8 +806,7 @@ QUOTAS = QuotaEngine() resources = [ ReservableResource('shares', _sync_shares, 'quota_shares'), - # TODO(yportnova): Uncomment when Snapshot size is implemented - # ReservableResource('snapshots', _sync_snapshots, 'quota_snapshots'), + ReservableResource('snapshots', _sync_snapshots, 'quota_snapshots'), ReservableResource('gigabytes', _sync_gigabytes, 'quota_gigabytes'), ] diff --git a/manila/share/api.py b/manila/share/api.py index 954f72accd..47e191b875 100644 --- a/manila/share/api.py +++ b/manila/share/api.py @@ -227,6 +227,35 @@ class API(base.Base): msg = _("must be available") raise exception.InvalidShare(reason=msg) + size = share['size'] + + try: + reservations = QUOTAS.reserve(context, snapshots=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 snapshot (%(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 'snapshots' in overs: + msg = _("Quota exceeded for %(s_pid)s, tried to create " + "snapshot (%(d_consumed)d snapshots " + "already consumed)") + LOG.warn(msg % {'s_pid': context.project_id, + 'd_consumed': _consumed('snapshots')}) + raise exception.SnapshotLimitExceeded( + allowed=quotas['snapshots']) options = {'share_id': share['id'], 'size': share['size'], 'user_id': context.user_id, @@ -239,7 +268,16 @@ class API(base.Base): 'share_proto': share['share_proto'], 'export_location': share['export_location']} - snapshot = self.db.share_snapshot_create(context, options) + try: + snapshot = self.db.share_snapshot_create(context, options) + QUOTAS.commit(context, reservations) + except Exception: + with excutils.save_and_reraise_exception(): + try: + self.db.snapshot_delete(context, share['id']) + finally: + QUOTAS.rollback(context, reservations) + self.share_rpcapi.create_snapshot(context, share, snapshot) return snapshot diff --git a/manila/share/manager.py b/manila/share/manager.py index 8835621d92..a38fa35adc 100644 --- a/manila/share/manager.py +++ b/manila/share/manager.py @@ -185,8 +185,14 @@ class ShareManager(manager.SchedulerDependentManager): def delete_snapshot(self, context, snapshot_id): """Delete share snapshot.""" + context = context.elevated() snapshot_ref = self.db.share_snapshot_get(context, snapshot_id) + if context.project_id != snapshot_ref['project_id']: + project_id = snapshot_ref['project_id'] + else: + project_id = context.project_id + try: self.driver.delete_snapshot(context, snapshot_ref) except exception.ShareSnapshotIsBusy: @@ -198,6 +204,17 @@ class ShareManager(manager.SchedulerDependentManager): {'status': 'error_deleting'}) else: self.db.share_snapshot_destroy(context, snapshot_id) + try: + reservations = QUOTAS.reserve(context, + project_id=project_id, + shares=-1, + gigabytes=-snapshot_ref['size']) + except Exception: + reservations = None + LOG.exception(_("Failed to update usages deleting snapshot")) + + if reservations: + QUOTAS.commit(context, reservations, project_id=project_id) def allow_access(self, context, access_id): """Allow access to some share."""