diff options
author | wangxiyuan <wangxiyuan@huawei.com> | 2018-06-08 16:45:56 +0800 |
---|---|---|
committer | wangxiyuan <wangxiyuan@huawei.com> | 2018-07-17 11:59:58 +0800 |
commit | 4b4835a01c662ddb11e9f1aace47a02c6e337978 (patch) | |
tree | 4fb7dc65885ada9e06a29a87d5d71902f5387a6b /keystone/limit | |
parent | 899e691c9f873631f51f32c6e8131ad7986fec81 (diff) | |
download | keystone-4b4835a01c662ddb11e9f1aace47a02c6e337978.tar.gz |
Strict two level limit model
This patch introduced the hierarchical limit structure
into Keystone.
The strict two level enforcement model is added as well.
Change-Id: Ic80e435a14ad7d6d4eccd4cd6365fb2d99fd26c1
bp: strict-two-level-model
Diffstat (limited to 'keystone/limit')
-rw-r--r-- | keystone/limit/core.py | 17 | ||||
-rw-r--r-- | keystone/limit/models/__init__.py | 29 | ||||
-rw-r--r-- | keystone/limit/models/base.py | 56 | ||||
-rw-r--r-- | keystone/limit/models/flat.py | 16 | ||||
-rw-r--r-- | keystone/limit/models/strict_two_level.py | 128 |
5 files changed, 205 insertions, 41 deletions
diff --git a/keystone/limit/core.py b/keystone/limit/core.py index 61021c81c..3c2109d55 100644 --- a/keystone/limit/core.py +++ b/keystone/limit/core.py @@ -11,6 +11,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import copy from keystone.common import cache from keystone.common import driver_hints @@ -18,8 +19,7 @@ from keystone.common import manager from keystone.common import provider_api import keystone.conf from keystone import exception -from keystone.limit import models - +from keystone.limit.models import base CONF = keystone.conf.CONF PROVIDERS = provider_api.ProviderAPIs @@ -36,9 +36,8 @@ class Manager(manager.Manager): unified_limit_driver = CONF.unified_limit.driver super(Manager, self).__init__(unified_limit_driver) - self.enforcement_model = models.get_enforcement_model_from_config( - CONF.unified_limit.enforcement_model - ) + self.enforcement_model = base.load_driver( + CONF.unified_limit.enforcement_model) def _assert_resource_exist(self, unified_limit, target): try: @@ -64,8 +63,8 @@ class Manager(manager.Manager): def get_model(self): """Return information of the configured enforcement model.""" return { - 'name': self.enforcement_model.name, - 'description': self.enforcement_model.description + 'name': self.enforcement_model.NAME, + 'description': self.enforcement_model.DESCRIPTION } def create_registered_limits(self, registered_limits): @@ -97,10 +96,14 @@ class Manager(manager.Manager): def create_limits(self, limits): for limit in limits: self._assert_resource_exist(limit, 'limit') + self.enforcement_model.check_limit(copy.deepcopy(limits)) return self.driver.create_limits(limits) def update_limit(self, limit_id, limit): self._assert_resource_exist(limit, 'limit') + limit_ref = self.get_limit(limit_id) + limit_ref.update(limit) + self.enforcement_model.check_limit(copy.deepcopy([limit_ref])) updated_limit = self.driver.update_limit(limit_id, limit) self.get_limit.invalidate(self, updated_limit['id']) return updated_limit diff --git a/keystone/limit/models/__init__.py b/keystone/limit/models/__init__.py index 5fc936fd4..e69de29bb 100644 --- a/keystone/limit/models/__init__.py +++ b/keystone/limit/models/__init__.py @@ -1,29 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from keystone.limit.models import flat - - -def get_enforcement_model_from_config(enforcement_model): - """Factory that returns an enforcement model object based on configuration. - - :param enforcement_model str: A string, usually from a configuration - option, representing the name of the - enforcement model - :returns: an `Model` object - - """ - # NOTE(lbragstad): The configuration option set is strictly checked by the - # ``oslo.config`` object. If someone passes in a garbage value, it will - # fail before it gets to this point. - if enforcement_model == 'flat': - return flat.Model() diff --git a/keystone/limit/models/base.py b/keystone/limit/models/base.py new file mode 100644 index 000000000..1b3aaaf87 --- /dev/null +++ b/keystone/limit/models/base.py @@ -0,0 +1,56 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import abc + +import six +import stevedore + +import keystone.conf +from keystone.i18n import _ + +CONF = keystone.conf.CONF + + +def load_driver(driver_name, *args): + namespace = 'keystone.unified_limit.model' + try: + driver_manager = stevedore.DriverManager(namespace, + driver_name, + invoke_on_load=True, + invoke_args=args) + return driver_manager.driver + except stevedore.exception.NoMatches: + msg = (_('Unable to find %(name)r driver in %(namespace)r.')) + raise ImportError(msg % {'name': driver_name, 'namespace': namespace}) + + +@six.add_metaclass(abc.ABCMeta) +class ModelBase(object): + """Interface for a limit model driver.""" + + NAME = None + DESCRIPTION = None + MAX_PROJECT_TREE_DEPTH = None + + def check_limit(self, limits): + """Check the new creating or updating limits if satisfy the model. + + :param limits: A list of the limit objects need to be check. + :type limits: A list of the limits. Each limit is a dict that contains + "resource_limit", "resource_name", "project_id", "service_id" and + optional "region_id", "description". + + :raises keystone.exception.InvalidLimit: If any of the input limits + doesn't satisfy the limit model. + + """ + raise NotImplementedError() diff --git a/keystone/limit/models/flat.py b/keystone/limit/models/flat.py index 68a864236..c20516059 100644 --- a/keystone/limit/models/flat.py +++ b/keystone/limit/models/flat.py @@ -10,13 +10,19 @@ # License for the specific language governing permissions and limitations # under the License. +from keystone.limit.models import base -# TODO(lbragstad): This should inherit from an abstract interface so that we -# ensure all models implement the same things. -class Model(object): - name = 'flat' - description = ( +class FlatModel(base.ModelBase): + + NAME = 'flat' + DESCRIPTION = ( 'Limit enforcement and validation does not take project hierarchy ' 'into consideration.' ) + MAX_PROJECT_TREE_DEPTH = None + + def check_limit(self, limits): + # Flat limit model is not hierarchical, so don't need to check the + # value. + return diff --git a/keystone/limit/models/strict_two_level.py b/keystone/limit/models/strict_two_level.py new file mode 100644 index 000000000..95085bed9 --- /dev/null +++ b/keystone/limit/models/strict_two_level.py @@ -0,0 +1,128 @@ +# Copyright 2018 Huawei +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_log import log + +from keystone.common import driver_hints +from keystone.common import provider_api +from keystone import exception +from keystone.limit.models import base + +LOG = log.getLogger(__name__) +PROVIDERS = provider_api.ProviderAPIs + + +class StrictTwoLevelModel(base.ModelBase): + NAME = 'strict_two_level' + DESCRIPTION = ( + 'This model requires project hierarchy never exceeds a depth of two' + ) + MAX_PROJECT_TREE_DEPTH = 2 + + def _get_specified_limit_value(self, project_id, resource_name, service_id, + region_id, is_parent=True): + """Get the specified limit value. + + Try to give the resource limit first. If the specified limit is a + parent in a project tree and the resource limit value is None, get the + related registered limit value instead. + + """ + hints = driver_hints.Hints() + hints.add_filter('project_id', project_id) + hints.add_filter('service_id', service_id) + hints.add_filter('resource_name', resource_name) + hints.add_filter('region_id', region_id) + limits = PROVIDERS.unified_limit_api.list_limits(hints) + limit_value = limits[0]['resource_limit'] if limits else None + if not limits and is_parent: + hints = driver_hints.Hints() + hints.add_filter('service_id', service_id) + hints.add_filter('resource_name', resource_name) + hints.add_filter('region_id', region_id) + limits = PROVIDERS.unified_limit_api.list_registered_limits(hints) + limit_value = limits[0]['default_limit'] if limits else None + return limit_value + + def _check_limit(self, project_id, resource_name, resource_limit, + service_id, region_id, parent_id): + """Check the specified limit value satisfies the related project tree. + + 1. Ensure the limit is smaller than its parent. + 2. Ensure the limit is bigger than its children. + + """ + if parent_id: + parent_limit_value = self._get_specified_limit_value( + parent_id, resource_name, service_id, region_id) + if parent_limit_value and resource_limit > parent_limit_value: + raise exception.InvalidLimit( + reason="Limit is bigger than parent.") + + sub_projects = PROVIDERS.resource_api.list_projects_in_subtree( + project_id) + for sub_project in sub_projects: + sub_limit_value = self._get_specified_limit_value( + sub_project['id'], resource_name, service_id, region_id, + is_parent=False) + if sub_limit_value and resource_limit < sub_limit_value: + raise exception.InvalidLimit( + reason="Limit is smaller than child.") + + def check_limit(self, limits): + """Check the input limits satisfy the related project tree or not. + + 1. Ensure the input is legal. + 2. Ensure the input will not break the exist limit tree. + + """ + for limit in limits: + project_id = limit['project_id'] + resource_name = limit['resource_name'] + resource_limit = limit['resource_limit'] + service_id = limit['service_id'] + region_id = limit.get('region_id') + try: + parent_project = PROVIDERS.resource_api.list_project_parents( + project_id)[0] + if not parent_project['is_domain']: + parent_id = parent_project['id'] + parent_limit = list(filter( + lambda x: x['project_id'] == parent_id, limits)) + if parent_limit: + if resource_limit > parent_limit[0]['resource_limit']: + raise exception.InvalidLimit( + reason="The input hierarchy tree is invalid.") + # The limit's parent is in request body, no need to + # check the backend any more. + continue + else: + parent_id = None + + self._check_limit(project_id, resource_name, resource_limit, + service_id, region_id, parent_id) + except exception.InvalidLimit: + error = ("The resource limit (project_id: %(project_id)s, " + "resource_name: %(resource_name)s, " + "resource_limit: %(resource_limit)s, " + "service_id: %(service_id)s, " + "region_id: %(region_id)s) doesn't satisfy " + "current hierarchy model.") % { + 'project_id': project_id, + 'resource_name': resource_name, + 'resource_limit': resource_limit, + 'service_id': service_id, + 'region_id': region_id + } + LOG.error(error) + raise exception.InvalidLimit(reason=error) |