summaryrefslogtreecommitdiff
path: root/keystone/limit
diff options
context:
space:
mode:
authorwangxiyuan <wangxiyuan@huawei.com>2018-06-08 16:45:56 +0800
committerwangxiyuan <wangxiyuan@huawei.com>2018-07-17 11:59:58 +0800
commit4b4835a01c662ddb11e9f1aace47a02c6e337978 (patch)
tree4fb7dc65885ada9e06a29a87d5d71902f5387a6b /keystone/limit
parent899e691c9f873631f51f32c6e8131ad7986fec81 (diff)
downloadkeystone-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.py17
-rw-r--r--keystone/limit/models/__init__.py29
-rw-r--r--keystone/limit/models/base.py56
-rw-r--r--keystone/limit/models/flat.py16
-rw-r--r--keystone/limit/models/strict_two_level.py128
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)