Pick between Flat and StrictTwoLevel enforcement
The enforcer needs to be able to determine which model to use and pass information to it. This commit sets the model attribute on the enforcer, adds a method to determine which model to use, and defines a basic interface for enforcement models to use. Change-Id: Id03d361b702c2ee0811f37ad23bb2b9a3171b1f7 Co-Authored-By: wangxiyuan <wangxiyuan1007@gmail.com> Co-Authored-By: Lance Bragstad <lbragstad@gmail.com> Co-Authored-By: John Garbutt <john.garbutt@stackhpc.com>
This commit is contained in:
parent
c02c025a66
commit
6e5b28a80d
@ -66,23 +66,49 @@ class Enforcer(object):
|
||||
|
||||
self.usage_callback = usage_callback
|
||||
self.connection = _get_keystone_connection()
|
||||
self.model = self._get_model_impl()
|
||||
|
||||
def enforce(self, project_id, deltas, resource_filters=None):
|
||||
"""Check resource usage against limits and request deltas.
|
||||
def _get_enforcement_model(self):
|
||||
"""Query keystone for the configured enforcement model."""
|
||||
return self.connection.get('/limits/model').json()['model']['name']
|
||||
|
||||
def _get_model_impl(self):
|
||||
"""get the enforcement model based on configured model in keystone."""
|
||||
model = self._get_enforcement_model()
|
||||
for impl in _MODELS:
|
||||
if model == impl.name:
|
||||
return impl()
|
||||
raise ValueError("enforcement model %s is not supported" % model)
|
||||
|
||||
def enforce(self, project_id, deltas):
|
||||
"""Check resource usage against limits for resources in deltas
|
||||
|
||||
From the deltas we extract the list of resource types that need to
|
||||
have limits enforced on them.
|
||||
|
||||
From keystone we fetch limits relating to this project_id and the
|
||||
endpoint specified in the configuration.
|
||||
|
||||
Using the usage_callback specified when creating the enforcer,
|
||||
we fetch the existing usage.
|
||||
|
||||
We then add the existing usage to the provided deltas to get
|
||||
the total proposed usage. This total proposed usage is then
|
||||
compared against all appropriate limits that apply.
|
||||
|
||||
Note if there are no registered limits for a given resource type,
|
||||
we fail the enforce in the same way as if we defaulted to
|
||||
a limit of zero, i.e. do not allow any use of a resource type
|
||||
that does not have a registered limit.
|
||||
|
||||
:param project_id: The project to check usage and enforce limits
|
||||
against.
|
||||
:type project_id: string
|
||||
:param deltas: An dictionary containing resource names as keys and
|
||||
requests resource quantities as values.
|
||||
requests resource quantities as positive integers.
|
||||
We only check limits for resources in deltas.
|
||||
Specify a quantity of zero to check current usage.
|
||||
:type deltas: dictionary
|
||||
:param resource_filters: A list of strings containing the resource
|
||||
names to filter the return values of the
|
||||
usage_callback. This is a performance
|
||||
optimization in the event the caller doesn't
|
||||
want the usage_callback to collect all
|
||||
resources owned by the service. By default,
|
||||
no resources will be filtered.
|
||||
|
||||
"""
|
||||
if not isinstance(project_id, six.text_type):
|
||||
@ -91,6 +117,30 @@ class Enforcer(object):
|
||||
if not isinstance(deltas, dict):
|
||||
msg = 'deltas must be a dictionary.'
|
||||
raise ValueError(msg)
|
||||
if resource_filters and not isinstance(resource_filters, list):
|
||||
msg = 'resource_filters must be a list.'
|
||||
raise ValueError(msg)
|
||||
|
||||
for k, v in iter(deltas.items()):
|
||||
if not isinstance(k, six.text_type):
|
||||
raise ValueError('resource name is not a string.')
|
||||
elif not isinstance(v, int):
|
||||
raise ValueError('resource limit is not an integer.')
|
||||
|
||||
self.model.enforce(project_id, deltas)
|
||||
|
||||
|
||||
class _FlatEnforcer(object):
|
||||
|
||||
name = 'flat'
|
||||
|
||||
def enforce(self, project_id, deltas):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class _StrictTwoLevelEnforcer(object):
|
||||
|
||||
name = 'strict-two-level'
|
||||
|
||||
def enforce(self, project_id, deltas):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
_MODELS = [_FlatEnforcer, _StrictTwoLevelEnforcer]
|
||||
|
@ -49,6 +49,9 @@ class TestEnforcer(base.BaseTestCase):
|
||||
)
|
||||
|
||||
limit._SDK_CONNECTION = mock.MagicMock()
|
||||
json = mock.MagicMock()
|
||||
json.json.return_value = {"model": {"name": "flat"}}
|
||||
limit._SDK_CONNECTION.get.return_value = json
|
||||
|
||||
def _get_usage_for_project(self, project_id):
|
||||
return 8
|
||||
@ -79,3 +82,24 @@ class TestEnforcer(base.BaseTestCase):
|
||||
project_id,
|
||||
invalid_delta
|
||||
)
|
||||
|
||||
def test_set_model_impl(self):
|
||||
enforcer = limit.Enforcer(self._get_usage_for_project)
|
||||
self.assertIsInstance(enforcer.model, limit._FlatEnforcer)
|
||||
|
||||
def test_get_model_impl(self):
|
||||
json = mock.MagicMock()
|
||||
limit._SDK_CONNECTION.get.return_value = json
|
||||
|
||||
json.json.return_value = {"model": {"name": "flat"}}
|
||||
enforcer = limit.Enforcer(self._get_usage_for_project)
|
||||
flat_impl = enforcer._get_model_impl()
|
||||
self.assertIsInstance(flat_impl, limit._FlatEnforcer)
|
||||
|
||||
json.json.return_value = {"model": {"name": "strict-two-level"}}
|
||||
flat_impl = enforcer._get_model_impl()
|
||||
self.assertIsInstance(flat_impl, limit._StrictTwoLevelEnforcer)
|
||||
|
||||
json.json.return_value = {"model": {"name": "foo"}}
|
||||
e = self.assertRaises(ValueError, enforcer._get_model_impl)
|
||||
self.assertEqual("enforcement model foo is not supported", str(e))
|
||||
|
Loading…
x
Reference in New Issue
Block a user