oslo.limit/oslo_limit/exception.py
John Garbutt 40ef2764a5 Add flat enforcer
Taking the Nova work as an example, looking to add a basic flat enforcer
that meets Nova's needs.

The user of the Enforce provides a callback. You can see an example of
the callback in the unit tests:

  def fake_callback(project_id, resources):
    return {
      "a": 5,
      "b": 10,
      "c": 0,
    }

In the code where you want to check if you increase the amount of
resources that are being consumed, you can make this call:

   enforcer = limit.Enforcer(fake_callback)
   enforcer.enforce(project_id, {"a": 5})

The enforce function looks up the limits that apply for the given
project_id, uses the callback to count the current usage. The proposed
usage is then calculated be adding the delta and the current usage
together. This is compared to any limits that apply.

If you later want to check if you have races that mean you are
over your limit, you can do this call:

   enforcer.enforce(project_id, {'a': 0})

Summary of key design points:

* single set of logic to enforce limits shared between all projects
  that adopt unified limits

* interface should work for both flat and strict-two-level enforcement

* it is assumed that oslo.limit will control which type of enforcement
  is being applied

* callback lists all resources that need counting
  for the given project_id, in Nova this helps limit
  the number of API calls made to placement

* allows to check if proposed additional usage means you are over
  your limit, and also double check if the current usages means
  you are over quota

* if the code is checking a resource where you do not have a
  registered limit, we default to a limit of zero, i.e. no
  resources can be created unless you set that registered limit
  There will be an appropriate warning logged to help the operator
  understand what needs to be setup in keystone.

This builds on various previous prototypes from:
Co-Authored-By: Lance Bragstad<lbragstad@gmail.com>
Co-Authored-By: wangxiyuan <wangxiyuan@huawei.com>

Change-Id: I294a922ea80af673291c991f07a4a203f25c289d
2019-11-25 18:30:28 +00:00

64 lines
2.3 KiB
Python

# -*- coding: utf-8 -*-
# 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_limit._i18n import _
class ProjectOverLimit(Exception):
def __init__(self, project_id, over_limit_info_list):
"""Exception raised when a project goes over one or more limits
:param project_id: the project id
:param over_limit_info_list: list of OverLimitInfo objects
"""
if not isinstance(over_limit_info_list, list):
raise ValueError(over_limit_info_list)
if len(over_limit_info_list) == 0:
raise ValueError(over_limit_info_list)
for info in over_limit_info_list:
if not isinstance(info, OverLimitInfo):
raise ValueError(over_limit_info_list)
self.project_id = project_id
self.over_limit_info_list = over_limit_info_list
msg = _("Project %(project_id)s is over a limit for "
"%(limits)s") % {'project_id': project_id,
'limits': over_limit_info_list}
super(ProjectOverLimit, self).__init__(msg)
class OverLimitInfo(object):
def __init__(self, resource_name, limit, current_usage, delta):
self.resource_name = resource_name
self.limit = int(limit)
self.current_usage = int(current_usage)
self.delta = int(delta)
def __str__(self):
template = ("Resource %s is over limit of %s due to "
"current usage %s and delta %s")
return template % (self.resource_name, self.limit,
self.current_usage, self.delta)
def __repr__(self):
return self.__str__()
class SessionInitError(Exception):
def __init__(self, reason):
msg = _(
"Can't initialise OpenStackSDK session: %(reason)s."
) % {'reason': reason}
super(SessionInitError, self).__init__(msg)