Make calculate_usage() work if limits are missing
The calculate_usage interface was added recently to allow consumers to probe limits and usage without requiring the enforce behavior workflow. If a limit was passed to it that was not registered in keystone, get_project_limits() would raise a ProjectOverLimit exception itself to abort the process immediately, providing the "unregistered means zero" behavior. This works fine for the enforce workflow, but not the calculate one. This changes get_project_limits() to just return a zero limit for a missing one, which will be considered by the enforce workflow in the same way, keeping the existing behavior. It will merely be reported by the calculate workflow, which is the desired change. Change-Id: Iaab1f0d5eb0da9a667267537d86f6c70bc8db51d
This commit is contained in:
parent
ca8df2af67
commit
a49f3a04d0
@ -146,7 +146,10 @@ Another usage pattern is to check a limit and usage for a given
|
|||||||
project, outside the scope of enforcement. This may be useful in a
|
project, outside the scope of enforcement. This may be useful in a
|
||||||
reporting API to be able to expose to a user the limit and usage
|
reporting API to be able to expose to a user the limit and usage
|
||||||
information that the enforcer would use to judge a resource
|
information that the enforcer would use to judge a resource
|
||||||
consumption event.
|
consumption event. Any limit passed to this API which is not
|
||||||
|
registered in keystone will be considered to be zero, in keeping with
|
||||||
|
the behavior of the enforcer assuming that "unregistered means no
|
||||||
|
quota."
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
This should ideally not be used to provide your own enforcement of
|
This should ideally not be used to provide your own enforcement of
|
||||||
|
@ -280,29 +280,21 @@ class _EnforcerUtils(object):
|
|||||||
def get_project_limits(self, project_id, resource_names):
|
def get_project_limits(self, project_id, resource_names):
|
||||||
"""Get all the limits for given project a resource_name list
|
"""Get all the limits for given project a resource_name list
|
||||||
|
|
||||||
We will raise ClaimExceedsLimit if no limit is found to ensure that
|
If a limit is not found, it will be considered to be zero
|
||||||
all clients of this library react to this situation in the same way.
|
(i.e. no quota)
|
||||||
|
|
||||||
:param project_id:
|
:param project_id:
|
||||||
:param resource_names: list of resource_name strings
|
:param resource_names: list of resource_name strings
|
||||||
:return: list of (resource_name,limit) pairs
|
:return: list of (resource_name,limit) pairs
|
||||||
|
|
||||||
:raises exception.ClaimExceedsLimit: if no limit is found
|
|
||||||
"""
|
"""
|
||||||
# Using a list to preserver the resource_name order
|
# Using a list to preserver the resource_name order
|
||||||
project_limits = []
|
project_limits = []
|
||||||
missing_limits = []
|
|
||||||
for resource_name in resource_names:
|
for resource_name in resource_names:
|
||||||
try:
|
try:
|
||||||
limit = self._get_limit(project_id, resource_name)
|
limit = self._get_limit(project_id, resource_name)
|
||||||
project_limits.append((resource_name, limit))
|
|
||||||
except _LimitNotFound:
|
except _LimitNotFound:
|
||||||
missing_limits.append(resource_name)
|
limit = 0
|
||||||
|
project_limits.append((resource_name, limit))
|
||||||
if len(missing_limits) > 0:
|
|
||||||
over_limit_list = [exception.OverLimitInfo(name, 0, 0, 0)
|
|
||||||
for name in missing_limits]
|
|
||||||
raise exception.ProjectOverLimit(project_id, over_limit_list)
|
|
||||||
|
|
||||||
return project_limits
|
return project_limits
|
||||||
|
|
||||||
|
@ -143,6 +143,38 @@ class TestEnforcer(base.BaseTestCase):
|
|||||||
self.assertEqual(expected, enforcer.calculate_usage(project_id,
|
self.assertEqual(expected, enforcer.calculate_usage(project_id,
|
||||||
['a', 'b']))
|
['a', 'b']))
|
||||||
|
|
||||||
|
@mock.patch.object(limit._EnforcerUtils, "_get_project_limit")
|
||||||
|
@mock.patch.object(limit._EnforcerUtils, "_get_registered_limit")
|
||||||
|
def test_calculate_and_enforce_some_missing(self, mock_get_reglimit,
|
||||||
|
mock_get_limit):
|
||||||
|
# Registered and project limits for a and b, c is unregistered
|
||||||
|
reg_limits = {'a': mock.MagicMock(default_limit=10),
|
||||||
|
'b': mock.MagicMock(default_limit=10)}
|
||||||
|
prj_limits = {('bar', 'b'): mock.MagicMock(resource_limit=6)}
|
||||||
|
mock_get_reglimit.side_effect = lambda r: reg_limits.get(r)
|
||||||
|
mock_get_limit.side_effect = lambda p, r: prj_limits.get((p, r))
|
||||||
|
|
||||||
|
# Regardless, we have usage for all three
|
||||||
|
mock_usage = mock.MagicMock()
|
||||||
|
mock_usage.return_value = {'a': 5, 'b': 5, 'c': 5}
|
||||||
|
|
||||||
|
enforcer = limit.Enforcer(mock_usage)
|
||||||
|
|
||||||
|
# When we calculate usage, we should expect the default limit
|
||||||
|
# of zero for the unregistered limit
|
||||||
|
expected = {
|
||||||
|
'a': limit.ProjectUsage(10, 5),
|
||||||
|
'b': limit.ProjectUsage(6, 5),
|
||||||
|
'c': limit.ProjectUsage(0, 5),
|
||||||
|
}
|
||||||
|
self.assertEqual(expected,
|
||||||
|
enforcer.calculate_usage('bar', ['a', 'b', 'c']))
|
||||||
|
|
||||||
|
# Make sure that if we enforce, we get the expected behavior
|
||||||
|
# of c being considered to be zero
|
||||||
|
self.assertRaises(exception.ProjectOverLimit,
|
||||||
|
enforcer.enforce, 'bar', {'a': 1, 'b': 0, 'c': 1})
|
||||||
|
|
||||||
def test_calculate_usage_bad_params(self):
|
def test_calculate_usage_bad_params(self):
|
||||||
enforcer = limit.Enforcer(mock.MagicMock())
|
enforcer = limit.Enforcer(mock.MagicMock())
|
||||||
|
|
||||||
@ -216,14 +248,17 @@ class TestFlatEnforcer(base.BaseTestCase):
|
|||||||
self.assertEqual(0, over_a.current_usage)
|
self.assertEqual(0, over_a.current_usage)
|
||||||
self.assertEqual(2, over_a.delta)
|
self.assertEqual(2, over_a.delta)
|
||||||
|
|
||||||
@mock.patch.object(limit._EnforcerUtils, "get_project_limits")
|
@mock.patch.object(limit._EnforcerUtils, "_get_project_limit")
|
||||||
def test_enforce_raises_on_missing_limit(self, mock_get_limits):
|
@mock.patch.object(limit._EnforcerUtils, "_get_registered_limit")
|
||||||
mock_usage = mock.MagicMock()
|
def test_enforce_raises_on_missing_limit(self, mock_get_reglimit,
|
||||||
|
mock_get_limit):
|
||||||
|
def mock_usage(*a):
|
||||||
|
return {'a': 1, 'b': 1}
|
||||||
|
|
||||||
project_id = uuid.uuid4().hex
|
project_id = uuid.uuid4().hex
|
||||||
deltas = {"a": 0, "b": 0}
|
deltas = {"a": 0, "b": 0}
|
||||||
mock_get_limits.side_effect = exception.ProjectOverLimit(
|
mock_get_reglimit.return_value = None
|
||||||
project_id, [exception.OverLimitInfo("a", 0, 0, 0)])
|
mock_get_limit.return_value = None
|
||||||
|
|
||||||
enforcer = limit._FlatEnforcer(mock_usage)
|
enforcer = limit._FlatEnforcer(mock_usage)
|
||||||
self.assertRaises(exception.ProjectOverLimit, enforcer.enforce,
|
self.assertRaises(exception.ProjectOverLimit, enforcer.enforce,
|
||||||
@ -292,24 +327,8 @@ class TestEnforcerUtils(base.BaseTestCase):
|
|||||||
limits = utils.get_project_limits(project_id, ["a", "b"])
|
limits = utils.get_project_limits(project_id, ["a", "b"])
|
||||||
self.assertEqual([('a', 1), ('b', 2)], limits)
|
self.assertEqual([('a', 1), ('b', 2)], limits)
|
||||||
|
|
||||||
e = self.assertRaises(exception.ProjectOverLimit,
|
limits = utils.get_project_limits(project_id, ["c", "d"])
|
||||||
utils.get_project_limits,
|
self.assertEqual([('c', 0), ('d', 0)], limits)
|
||||||
project_id, ["c", "d"])
|
|
||||||
expected = ("Project %s is over a limit for "
|
|
||||||
"[Resource c is over limit of 0 due to current usage 0 "
|
|
||||||
"and delta 0, "
|
|
||||||
"Resource d is over limit of 0 due to current usage 0 "
|
|
||||||
"and delta 0]")
|
|
||||||
self.assertEqual(expected % project_id, str(e))
|
|
||||||
self.assertEqual(project_id, e.project_id)
|
|
||||||
self.assertEqual(2, len(e.over_limit_info_list))
|
|
||||||
over_c = e.over_limit_info_list[0]
|
|
||||||
self.assertEqual("c", over_c.resource_name)
|
|
||||||
over_d = e.over_limit_info_list[1]
|
|
||||||
self.assertEqual("d", over_d.resource_name)
|
|
||||||
self.assertEqual(0, over_d.limit)
|
|
||||||
self.assertEqual(0, over_d.current_usage)
|
|
||||||
self.assertEqual(0, over_d.delta)
|
|
||||||
|
|
||||||
def test_get_limit_cache(self, cache=True):
|
def test_get_limit_cache(self, cache=True):
|
||||||
# No project limit and registered limit = 5
|
# No project limit and registered limit = 5
|
||||||
|
Loading…
Reference in New Issue
Block a user