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:
Lance Bragstad 2019-06-19 21:00:18 +00:00 committed by John Garbutt
parent c02c025a66
commit 6e5b28a80d
2 changed files with 87 additions and 13 deletions

View File

@ -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]

View File

@ -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))