diff --git a/manila/db/api.py b/manila/db/api.py index 318bf35e6a..4341924f2e 100644 --- a/manila/db/api.py +++ b/manila/db/api.py @@ -250,12 +250,13 @@ def quota_usage_update(context, project_id, user_id, resource, def quota_reserve(context, resources, quotas, user_quotas, share_type_quotas, deltas, expire, until_refresh, max_age, - project_id=None, user_id=None, share_type_id=None): + project_id=None, user_id=None, share_type_id=None, + overquota_allowed=False): """Check quotas and create appropriate reservations.""" return IMPL.quota_reserve( context, resources, quotas, user_quotas, share_type_quotas, deltas, expire, until_refresh, max_age, project_id=project_id, user_id=user_id, - share_type_id=share_type_id) + share_type_id=share_type_id, overquota_allowed=overquota_allowed) def reservation_commit(context, reservations, project_id=None, user_id=None, diff --git a/manila/db/sqlalchemy/api.py b/manila/db/sqlalchemy/api.py index 3e7a3dfeed..707ed6a90f 100644 --- a/manila/db/sqlalchemy/api.py +++ b/manila/db/sqlalchemy/api.py @@ -914,16 +914,19 @@ def _get_project_quota_usages(context, session, project_id): @require_context def quota_reserve(context, resources, project_quotas, user_quotas, share_type_quotas, deltas, expire, until_refresh, - max_age, project_id=None, user_id=None, share_type_id=None): + max_age, project_id=None, user_id=None, share_type_id=None, + overquota_allowed=False): user_reservations = _quota_reserve( context, resources, project_quotas, user_quotas, - deltas, expire, until_refresh, max_age, project_id, user_id=user_id) + deltas, expire, until_refresh, max_age, project_id, user_id=user_id, + overquota_allowed=overquota_allowed) if share_type_id: try: st_reservations = _quota_reserve( context, resources, project_quotas, share_type_quotas, deltas, expire, until_refresh, max_age, project_id, - share_type_id=share_type_id) + share_type_id=share_type_id, + overquota_allowed=overquota_allowed) except exception.OverQuota: with excutils.save_and_reraise_exception(): # rollback previous reservations @@ -937,7 +940,8 @@ def quota_reserve(context, resources, project_quotas, user_quotas, @oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True) def _quota_reserve(context, resources, project_quotas, user_or_st_quotas, deltas, expire, until_refresh, - max_age, project_id=None, user_id=None, share_type_id=None): + max_age, project_id=None, user_id=None, share_type_id=None, + overquota_allowed=False): elevated = context.elevated() session = get_session() with session.begin(): @@ -1085,6 +1089,21 @@ def _quota_reserve(context, resources, project_quotas, user_or_st_quotas, user_or_st_quotas[res] < delta + user_or_st_usages[res].total)] + # NOTE(carloss): If OverQuota is allowed, there is no problem to exceed + # the quotas, so we reset the overs list and LOG it. + if overs and overquota_allowed: + msg = _("The service has identified one or more exceeded " + "quotas. Please check the quotas for project " + "%(project_id)s, user %(user_id)s and share type " + "%(share_type_id)s, and adjust them if " + "necessary.") % { + "project_id": project_id, + "user_id": user_id, + "share_type_id": share_type_id + } + LOG.warning(msg) + overs = [] + # NOTE(Vek): The quota check needs to be in the transaction, # but the transaction doesn't fail just because # we're over quota, so the OverQuota raise is diff --git a/manila/quota.py b/manila/quota.py index 72ca8b101f..e2136d42a2 100644 --- a/manila/quota.py +++ b/manila/quota.py @@ -378,7 +378,8 @@ class DbQuotaDriver(object): return {k: v['limit'] for k, v in quotas.items()} def reserve(self, context, resources, deltas, expire=None, - project_id=None, user_id=None, share_type_id=None): + project_id=None, user_id=None, share_type_id=None, + overquota_allowed=False): """Check quotas and reserve resources. For counting quotas--those quotas for which there is a usage @@ -459,7 +460,7 @@ class DbQuotaDriver(object): context, resources, quotas, user_quotas, share_type_quotas, deltas, expire, CONF.until_refresh, CONF.max_age, project_id=project_id, user_id=user_id, - share_type_id=share_type_id) + share_type_id=share_type_id, overquota_allowed=overquota_allowed) def commit(self, context, reservations, project_id=None, user_id=None, share_type_id=None): @@ -870,7 +871,7 @@ class QuotaEngine(object): return res.count(context, *args, **kwargs) def reserve(self, context, expire=None, project_id=None, user_id=None, - share_type_id=None, **deltas): + share_type_id=None, overquota_allowed=False, **deltas): """Check quotas and reserve resources. For counting quotas--those quotas for which there is a usage @@ -911,6 +912,7 @@ class QuotaEngine(object): project_id=project_id, user_id=user_id, share_type_id=share_type_id, + overquota_allowed=overquota_allowed ) LOG.debug("Created reservations %s", reservations) diff --git a/manila/share/manager.py b/manila/share/manager.py index 7c3179cec0..86c77af0e5 100644 --- a/manila/share/manager.py +++ b/manila/share/manager.py @@ -2420,6 +2420,12 @@ class ShareManager(manager.SchedulerDependentManager): msg = _("Driver cannot calculate share size.") raise exception.InvalidShare(reason=msg) + # NOTE(carloss): Allowing OverQuota to do not compromise this + # operation. If this hit OverQuota error while managing a share, + # the admin would need to reset the state of the share and + # delete or force delete the share (bug 1863298). Allowing + # OverQuota makes this operation work properly and the admin will + # need to adjust quotas afterwards. reservations = QUOTAS.reserve( context, project_id=project_id, @@ -2427,6 +2433,7 @@ class ShareManager(manager.SchedulerDependentManager): shares=1, gigabytes=share_update['size'], share_type_id=share_instance['share_type_id'], + overquota_allowed=True ) QUOTAS.commit( context, reservations, project_id=project_id, diff --git a/manila/tests/share/test_manager.py b/manila/tests/share/test_manager.py index 5cc572e893..86499aa4a5 100644 --- a/manila/tests/share/test_manager.py +++ b/manila/tests/share/test_manager.py @@ -2621,6 +2621,14 @@ class ShareManagerTestCase(test.TestCase): share = db_utils.create_share(replication_type=replication_type) share_id = share['id'] driver_options = {'fake': 'fake'} + expected_deltas = { + 'project_id': share['project_id'], + 'user_id': self.context.user_id, + 'shares': 1, + 'gigabytes': driver_data['size'], + 'share_type_id': share['instance']['share_type_id'], + 'overquota_allowed': True + } self.share_manager.manage_share(self.context, share_id, driver_options) @@ -2644,6 +2652,8 @@ class ShareManagerTestCase(test.TestCase): self.share_manager.db.share_update.assert_called_once_with( utils.IsAMatcher(context.RequestContext), share_id, valid_share_data) + quota.QUOTAS.reserve.assert_called_once_with( + mock.ANY, **expected_deltas) def test_update_quota_usages_new(self): self.mock_object(self.share_manager.db, 'quota_usage_get', diff --git a/manila/tests/test_quota.py b/manila/tests/test_quota.py index 512a800ff6..ed6efe024c 100644 --- a/manila/tests/test_quota.py +++ b/manila/tests/test_quota.py @@ -353,6 +353,7 @@ class DbQuotaDriverTestCase(test.TestCase): 'project_id': self.ctxt.project_id, 'user_id': self.ctxt.user_id, 'share_type_id': None, + 'overquota_allowed': False } expected_kwargs.update(kwargs) st_quotas = st_quotas if kwargs.get('share_type_id') else {} @@ -624,7 +625,8 @@ class QuotaEngineTestCase(test.TestCase): self.driver.reserve.assert_called_once_with( self.ctxt, self.engine._resources, {'delta1': 1, 'delta2': 2}, expire='fake_expire', project_id=self.project_id, - user_id=self.user_id, share_type_id=self.share_type_id) + user_id=self.user_id, share_type_id=self.share_type_id, + overquota_allowed=False) @ddt.data(Exception('FakeException'), [None]) def test_commit(self, side_effect): diff --git a/releasenotes/notes/bug-1863298-fix-manage-overquota-issue-37031a593b66f8ba.yaml b/releasenotes/notes/bug-1863298-fix-manage-overquota-issue-37031a593b66f8ba.yaml new file mode 100644 index 0000000000..e21d46ff97 --- /dev/null +++ b/releasenotes/notes/bug-1863298-fix-manage-overquota-issue-37031a593b66f8ba.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + Fixed an issue while bringing shares under Manila management. Now, when + a share is being managed and there is no available quota to complete + this operation, the service will allow the quotas to be exceeded and the + operation will be completed. The administrator will need to adjust the + quotas after. Please see + `Launchpad bug `_ for + more details.