def quota_reserve(context, resources, quotas, deltas, expire,
- until_refresh, max_age):
+ until_refresh, max_age, project_id=None):
"""Check quotas and create appropriate reservations."""
return IMPL.quota_reserve(context, resources, quotas, deltas, expire,
- until_refresh, max_age)
+ until_refresh, max_age, project_id=project_id)
-def reservation_commit(context, reservations):
+def reservation_commit(context, reservations, project_id=None):
"""Commit quota reservations."""
- return IMPL.reservation_commit(context, reservations)
+ return IMPL.reservation_commit(context, reservations,
+ project_id=project_id)
-def reservation_rollback(context, reservations):
+def reservation_rollback(context, reservations, project_id=None):
"""Roll back quota reservations."""
- return IMPL.reservation_rollback(context, reservations)
+ return IMPL.reservation_rollback(context, reservations,
+ project_id=project_id)
def quota_destroy_all_by_project(context, project_id):
# code always acquires the lock on quota_usages before acquiring the lock
# on reservations.
-def _get_quota_usages(context, session):
+def _get_quota_usages(context, session, project_id):
# Broken out for testability
rows = model_query(context, models.QuotaUsage,
read_deleted="no",
session=session).\
- filter_by(project_id=context.project_id).\
+ filter_by(project_id=project_id).\
with_lockmode('update').\
all()
return dict((row.resource, row) for row in rows)
@require_context
def quota_reserve(context, resources, quotas, deltas, expire,
- until_refresh, max_age):
+ until_refresh, max_age, project_id=None):
elevated = context.elevated()
session = get_session()
with session.begin():
+ if project_id is None:
+ project_id = context.project_id
+
# Get the current usages
- usages = _get_quota_usages(context, session)
+ usages = _get_quota_usages(context, session, project_id)
# Handle usage refresh
work = set(deltas.keys())
refresh = False
if resource not in usages:
usages[resource] = quota_usage_create(elevated,
- context.project_id,
+ project_id,
resource,
0, 0,
until_refresh or None,
# Grab the sync routine
sync = resources[resource].sync
- updates = sync(elevated, context.project_id, session)
+ updates = sync(elevated, project_id, session)
for res, in_use in updates.items():
# Make sure we have a destination for the usage!
if res not in usages:
usages[res] = quota_usage_create(elevated,
- context.project_id,
+ project_id,
res,
0, 0,
until_refresh or None,
reservation = reservation_create(elevated,
str(uuid.uuid4()),
usages[resource],
- context.project_id,
+ project_id,
resource, delta, expire,
session=session)
reservations.append(reservation.uuid)
@require_context
-def reservation_commit(context, reservations):
+def reservation_commit(context, reservations, project_id=None):
session = get_session()
with session.begin():
- usages = _get_quota_usages(context, session)
+ usages = _get_quota_usages(context, session, project_id)
for reservation in _quota_reservations(session, context, reservations):
usage = usages[reservation.resource]
@require_context
-def reservation_rollback(context, reservations):
+def reservation_rollback(context, reservations, project_id=None):
session = get_session()
with session.begin():
- usages = _get_quota_usages(context, session)
+ usages = _get_quota_usages(context, session, project_id)
for reservation in _quota_reservations(session, context, reservations):
usage = usages[reservation.resource]
return quotas
- def _get_quotas(self, context, resources, keys, has_sync):
+ def _get_quotas(self, context, resources, keys, has_sync, project_id=None):
"""
A helper method which retrieves the quotas for the specific
resources identified by keys, and which apply to the current
have a sync attribute; if False, indicates
that the resource must NOT have a sync
attribute.
+ :param project_id: Specify the project_id if current context
+ is admin and admin wants to impact on
+ common user's tenant.
"""
# Filter resources
# Grab and return the quotas (without usages)
quotas = self.get_project_quotas(context, sub_resources,
- context.project_id,
+ project_id,
context.quota_class, usages=False)
return dict((k, v['limit']) for k, v in quotas.items())
- def limit_check(self, context, resources, values):
+ def limit_check(self, context, resources, values, project_id=None):
"""Check simple quota limits.
For limits--those quotas for which there is no usage
:param resources: A dictionary of the registered resources.
:param values: A dictionary of the values to check against the
quota.
+ :param project_id: Specify the project_id if current context
+ is admin and admin wants to impact on
+ common user's tenant.
"""
# Ensure no value is less than zero
if unders:
raise exception.InvalidQuotaValue(unders=sorted(unders))
+ # If project_id is None, then we use the project_id in context
+ if project_id is None:
+ project_id = context.project_id
+
# Get the applicable quotas
quotas = self._get_quotas(context, resources, values.keys(),
- has_sync=False)
+ has_sync=False, project_id=project_id)
# Check the quotas and construct a list of the resources that
# would be put over limit by the desired values
overs = [key for key, val in values.items()
raise exception.OverQuota(overs=sorted(overs), quotas=quotas,
usages={})
- def reserve(self, context, resources, deltas, expire=None):
+ def reserve(self, context, resources, deltas, expire=None,
+ project_id=None):
"""Check quotas and reserve resources.
For counting quotas--those quotas for which there is a usage
default expiration time set by
--default-reservation-expire will be used (this
value will be treated as a number of seconds).
+ :param project_id: Specify the project_id if current context
+ is admin and admin wants to impact on
+ common user's tenant.
"""
# Set up the reservation expiration
if not isinstance(expire, datetime.datetime):
raise exception.InvalidReservationExpiration(expire=expire)
+ # If project_id is None, then we use the project_id in context
+ if project_id is None:
+ project_id = context.project_id
+
# Get the applicable quotas.
# NOTE(Vek): We're not worried about races at this point.
# Yes, the admin may be in the process of reducing
# quotas, but that's a pretty rare thing.
quotas = self._get_quotas(context, resources, deltas.keys(),
- has_sync=True)
+ has_sync=True, project_id=project_id)
# NOTE(Vek): Most of the work here has to be done in the DB
# API, because we have to do it in a transaction,
# session isn't available outside the DBAPI, we
# have to do the work there.
return db.quota_reserve(context, resources, quotas, deltas, expire,
- FLAGS.until_refresh, FLAGS.max_age)
+ FLAGS.until_refresh, FLAGS.max_age,
+ project_id=project_id)
- def commit(self, context, reservations):
+ def commit(self, context, reservations, project_id=None):
"""Commit reservations.
:param context: The request context, for access checks.
:param reservations: A list of the reservation UUIDs, as
returned by the reserve() method.
+ :param project_id: Specify the project_id if current context
+ is admin and admin wants to impact on
+ common user's tenant.
"""
+ # If project_id is None, then we use the project_id in context
+ if project_id is None:
+ project_id = context.project_id
- db.reservation_commit(context, reservations)
+ db.reservation_commit(context, reservations, project_id=project_id)
- def rollback(self, context, reservations):
+ def rollback(self, context, reservations, project_id=None):
"""Roll back reservations.
:param context: The request context, for access checks.
:param reservations: A list of the reservation UUIDs, as
returned by the reserve() method.
+ :param project_id: Specify the project_id if current context
+ is admin and admin wants to impact on
+ common user's tenant.
"""
+ # If project_id is None, then we use the project_id in context
+ if project_id is None:
+ project_id = context.project_id
- db.reservation_rollback(context, reservations)
+ db.reservation_rollback(context, reservations, project_id=project_id)
def destroy_all_by_project(self, context, project_id):
"""
return res.count(context, *args, **kwargs)
- def limit_check(self, context, **values):
+ def limit_check(self, context, project_id=None, **values):
"""Check simple quota limits.
For limits--those quotas for which there is no usage
nothing.
:param context: The request context, for access checks.
+ :param project_id: Specify the project_id if current context
+ is admin and admin wants to impact on
+ common user's tenant.
"""
- return self._driver.limit_check(context, self._resources, values)
+ return self._driver.limit_check(context, self._resources, values,
+ project_id=project_id)
- def reserve(self, context, expire=None, **deltas):
+ def reserve(self, context, expire=None, project_id=None, **deltas):
"""Check quotas and reserve resources.
For counting quotas--those quotas for which there is a usage
default expiration time set by
--default-reservation-expire will be used (this
value will be treated as a number of seconds).
+ :param project_id: Specify the project_id if current context
+ is admin and admin wants to impact on
+ common user's tenant.
"""
reservations = self._driver.reserve(context, self._resources, deltas,
- expire=expire)
+ expire=expire,
+ project_id=project_id)
LOG.debug(_("Created reservations %(reservations)s") % locals())
return reservations
- def commit(self, context, reservations):
+ def commit(self, context, reservations, project_id=None):
"""Commit reservations.
:param context: The request context, for access checks.
:param reservations: A list of the reservation UUIDs, as
returned by the reserve() method.
+ :param project_id: Specify the project_id if current context
+ is admin and admin wants to impact on
+ common user's tenant.
"""
try:
- self._driver.commit(context, reservations)
+ self._driver.commit(context, reservations, project_id=project_id)
except Exception:
# NOTE(Vek): Ignoring exceptions here is safe, because the
# usage resynchronization and the reservation expiration
LOG.exception(_("Failed to commit reservations "
"%(reservations)s") % locals())
- def rollback(self, context, reservations):
+ def rollback(self, context, reservations, project_id=None):
"""Roll back reservations.
:param context: The request context, for access checks.
:param reservations: A list of the reservation UUIDs, as
returned by the reserve() method.
+ :param project_id: Specify the project_id if current context
+ is admin and admin wants to impact on
+ common user's tenant.
"""
try:
- self._driver.rollback(context, reservations)
+ self._driver.rollback(context, reservations, project_id=project_id)
except Exception:
# NOTE(Vek): Ignoring exceptions here is safe, because the
# usage resynchronization and the reservation expiration
project_id, quota_class, defaults, usages))
return resources
- def limit_check(self, context, resources, values):
- self.called.append(('limit_check', context, resources, values))
-
- def reserve(self, context, resources, deltas, expire=None):
- self.called.append(('reserve', context, resources, deltas, expire))
+ def limit_check(self, context, resources, values, project_id=None):
+ self.called.append(('limit_check', context, resources,
+ values, project_id))
+
+ def reserve(self, context, resources, deltas, expire=None,
+ project_id=None):
+ self.called.append(('reserve', context, resources, deltas,
+ expire, project_id))
return self.reservations
- def commit(self, context, reservations):
- self.called.append(('commit', context, reservations))
+ def commit(self, context, reservations, project_id=None):
+ self.called.append(('commit', context, reservations, project_id))
- def rollback(self, context, reservations):
- self.called.append(('rollback', context, reservations))
+ def rollback(self, context, reservations, project_id=None):
+ self.called.append(('rollback', context, reservations, project_id))
def destroy_all_by_project(self, context, project_id):
self.called.append(('destroy_all_by_project', context, project_id))
test_resource1=4,
test_resource2=3,
test_resource3=2,
- test_resource4=1,)), ])
+ test_resource4=1,),
+ None), ])
def test_reserve(self):
context = FakeContext(None, None)
result2 = quota_obj.reserve(context, expire=3600,
test_resource1=1, test_resource2=2,
test_resource3=3, test_resource4=4)
+ result3 = quota_obj.reserve(context, project_id='fake_project',
+ test_resource1=1, test_resource2=2,
+ test_resource3=3, test_resource4=4)
self.assertEqual(driver.called, [
('reserve',
test_resource2=3,
test_resource3=2,
test_resource4=1, ),
+ None,
None),
('reserve',
context,
test_resource2=2,
test_resource3=3,
test_resource4=4, ),
- 3600), ])
+ 3600,
+ None),
+ ('reserve',
+ context,
+ quota_obj._resources,
+ dict(
+ test_resource1=1,
+ test_resource2=2,
+ test_resource3=3,
+ test_resource4=4, ),
+ None,
+ 'fake_project'), ])
self.assertEqual(result1, ['resv-01',
'resv-02',
'resv-03',
'resv-02',
'resv-03',
'resv-04', ])
+ self.assertEqual(result3, ['resv-01',
+ 'resv-02',
+ 'resv-03',
+ 'resv-04', ])
def test_commit(self):
context = FakeContext(None, None)
context,
['resv-01',
'resv-02',
- 'resv-03']), ])
+ 'resv-03'],
+ None), ])
def test_rollback(self):
context = FakeContext(None, None)
context,
['resv-01',
'resv-02',
- 'resv-03']), ])
+ 'resv-03'],
+ None), ])
def test_destroy_all_by_project(self):
context = FakeContext(None, None)
def _stub_quota_reserve(self):
def fake_quota_reserve(context, resources, quotas, deltas, expire,
- until_refresh, max_age):
+ until_refresh, max_age, project_id=None):
self.calls.append(('quota_reserve', expire, until_refresh,
max_age))
return ['resv-1', 'resv-2', 'resv-3']
def fake_get_session():
return FakeSession()
- def fake_get_quota_usages(context, session):
+ def fake_get_quota_usages(context, session, project_id):
return self.usages.copy()
def fake_quota_usage_create(context, project_id, resource, in_use,
def test_create_delete_volume(self):
"""Test volume can be created and deleted."""
# Need to stub out reserve, commit, and rollback
- def fake_reserve(context, expire=None, **deltas):
+ def fake_reserve(context, expire=None, project_id=None, **deltas):
return ["RESERVATION"]
- def fake_commit(context, reservations):
+ def fake_commit(context, reservations, project_id=None):
pass
- def fake_rollback(context, reservations):
+ def fake_rollback(context, reservations, project_id=None):
pass
self.stubs.Set(QUOTAS, "reserve", fake_reserve)
def test_create_volume_with_volume_type(self):
"""Test volume creation with default volume type."""
- def fake_reserve(context, expire=None, **deltas):
+ def fake_reserve(context, expire=None, project_id=None, **deltas):
return ["RESERVATION"]
- def fake_commit(context, reservations):
+ def fake_commit(context, reservations, project_id=None):
pass
- def fake_rollback(context, reservations):
+ def fake_rollback(context, reservations, project_id=None):
pass
self.stubs.Set(QUOTAS, "reserve", fake_reserve)
'name', 'description', image_id=1)
def _do_test_create_volume_with_size(self, size):
- def fake_reserve(context, expire=None, **deltas):
+ def fake_reserve(context, expire=None, project_id=None, **deltas):
return ["RESERVATION"]
- def fake_commit(context, reservations):
+ def fake_commit(context, reservations, project_id=None):
pass
- def fake_rollback(context, reservations):
+ def fake_rollback(context, reservations, project_id=None):
pass
self.stubs.Set(QUOTAS, "reserve", fake_reserve)
self._do_test_create_volume_with_size('2')
def test_create_volume_with_bad_size(self):
- def fake_reserve(context, expire=None, **deltas):
+ def fake_reserve(context, expire=None, project_id=None, **deltas):
return ["RESERVATION"]
- def fake_commit(context, reservations):
+ def fake_commit(context, reservations, project_id=None):
pass
- def fake_rollback(context, reservations):
+ def fake_rollback(context, reservations, project_id=None):
pass
self.stubs.Set(QUOTAS, "reserve", fake_reserve)
@wrap_check_policy
def delete(self, context, volume, force=False):
+ if context.is_admin and context.project_id != volume['project_id']:
+ project_id = volume['project_id']
+ else:
+ project_id = context.project_id
+
volume_id = volume['id']
if not volume['host']:
# NOTE(vish): scheduling failed, so delete it
# Note(zhiteng): update volume quota reservation
try:
- reservations = QUOTAS.reserve(context, volumes=-1,
+ reservations = QUOTAS.reserve(context,
+ project_id=project_id,
+ volumes=-1,
gigabytes=-volume['size'])
except Exception:
reservations = None
self.db.volume_destroy(context.elevated(), volume_id)
if reservations:
- QUOTAS.commit(context, reservations)
+ QUOTAS.commit(context, reservations, project_id=project_id)
return
if not force and volume['status'] not in ["available", "error",
"error_restoring"]:
"""Deletes and unexports volume."""
context = context.elevated()
volume_ref = self.db.volume_get(context, volume_id)
+
+ if context.project_id != volume_ref['project_id']:
+ project_id = volume_ref['project_id']
+ else:
+ project_id = context.project_id
+
LOG.info(_("volume %s: deleting"), volume_ref['name'])
if volume_ref['attach_status'] == "attached":
# Volume is still attached, need to detach first
# Get reservations
try:
- reservations = QUOTAS.reserve(context, volumes=-1,
+ reservations = QUOTAS.reserve(context,
+ project_id=project_id,
+ volumes=-1,
gigabytes=-volume_ref['size'])
except Exception:
reservations = None
# Commit the reservations
if reservations:
- QUOTAS.commit(context, reservations)
+ QUOTAS.commit(context, reservations, project_id=project_id)
return True
snapshot_ref = self.db.snapshot_get(context, snapshot_id)
LOG.info(_("snapshot %s: deleting"), snapshot_ref['name'])
+ if context.project_id != snapshot_ref['project_id']:
+ project_id = snapshot_ref['project_id']
+ else:
+ project_id = context.project_id
+
try:
LOG.debug(_("snapshot %s: deleting"), snapshot_ref['name'])
self.driver.delete_snapshot(snapshot_ref)
# Get reservations
try:
if CONF.no_snapshot_gb_quota:
- reservations = QUOTAS.reserve(context, snapshots=-1)
+ reservations = QUOTAS.reserve(context,
+ project_id=project_id,
+ snapshots=-1)
else:
reservations = QUOTAS.reserve(
context,
+ project_id=project_id,
snapshots=-1,
gigabytes=-snapshot_ref['volume_size'])
except Exception:
# Commit the reservations
if reservations:
- QUOTAS.commit(context, reservations)
+ QUOTAS.commit(context, reservations, project_id=project_id)
return True
def attach_volume(self, context, volume_id, instance_uuid, mountpoint):