trove/trove/quota/quota.py

378 lines
13 KiB
Python

# Copyright 2011 OpenStack Foundation
# All Rights Reserved.
#
# 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.
"""Quotas for DB instances and resources."""
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import importutils
import six
from trove.common import exception
from trove.common.i18n import _
from trove.quota.models import Quota
from trove.quota.models import QuotaUsage
from trove.quota.models import Reservation
from trove.quota.models import Resource
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class DbQuotaDriver(object):
"""
Driver to perform necessary checks to enforce quotas and obtain
quota information. The default driver utilizes the local
database.
"""
def __init__(self, resources):
self.resources = resources
def get_quota_by_tenant(self, tenant_id, resource):
"""Get a specific quota by tenant."""
quotas = Quota.find_all(tenant_id=tenant_id, resource=resource).all()
if len(quotas) == 0:
return Quota(tenant_id, resource, self.resources[resource].default)
return quotas[0]
def get_all_quotas_by_tenant(self, tenant_id, resources):
"""
Retrieve the quotas for the given tenant.
:param resources: A list of the registered resource to get.
:param tenant_id: The ID of the tenant to return quotas for.
"""
all_quotas = Quota.find_all(tenant_id=tenant_id).all()
result_quotas = {quota.resource: quota for quota in all_quotas
if quota.resource in resources}
if len(result_quotas) != len(resources):
for resource in resources:
# Not in the DB, return default value
if resource not in result_quotas:
quota = Quota(tenant_id,
resource,
self.resources[resource].default)
result_quotas[resource] = quota
return result_quotas
def get_quota_usage_by_tenant(self, tenant_id, resource):
"""Get a specific quota usage by tenant."""
quotas = QuotaUsage.find_all(tenant_id=tenant_id,
resource=resource).all()
if len(quotas) == 0:
return QuotaUsage.create(tenant_id=tenant_id,
in_use=0,
reserved=0,
resource=resource)
return quotas[0]
def get_all_quota_usages_by_tenant(self, tenant_id, resources):
"""
Retrieve the quota usagess for the given tenant.
:param tenant_id: The ID of the tenant to return quotas for.
:param resources: A list of the registered resources to get.
"""
all_usages = QuotaUsage.find_all(tenant_id=tenant_id).all()
result_usages = {usage.resource: usage for usage in all_usages
if usage.resource in resources}
if len(result_usages) != len(resources):
for resource in resources:
# Not in the DB, return default value
if resource not in result_usages:
usage = QuotaUsage.create(tenant_id=tenant_id,
in_use=0,
reserved=0,
resource=resource)
result_usages[resource] = usage
return result_usages
def get_defaults(self, resources):
"""Given a list of resources, retrieve the default quotas.
:param resources: A list of the registered resources.
"""
quotas = {}
for resource in resources.values():
quotas[resource.name] = resource.default
return quotas
def check_quotas(self, tenant_id, resources, deltas):
"""Check quotas for a tenant.
This method checks quotas against current usage,
reserved resources and the desired deltas.
If any of the proposed values is over the defined quota, an
QuotaExceeded exception will be raised with the sorted list of the
resources which are too high.
:param tenant_id: The ID of the tenant reserving the resources.
:param resources: A dictionary of the registered resources.
:param deltas: A dictionary of the proposed delta changes.
"""
unregistered_resources = [delta for delta in deltas
if delta not in resources]
if unregistered_resources:
raise exception.QuotaResourceUnknown(
unknown=unregistered_resources)
quotas = self.get_all_quotas_by_tenant(tenant_id, deltas.keys())
quota_usages = self.get_all_quota_usages_by_tenant(tenant_id,
deltas.keys())
overs = [resource for resource in deltas
if (int(deltas[resource]) > 0 and
quotas[resource].hard_limit >= 0 and
(quota_usages[resource].in_use +
quota_usages[resource].reserved +
int(deltas[resource])) > quotas[resource].hard_limit)]
if overs:
raise exception.QuotaExceeded(overs=sorted(overs))
def reserve(self, tenant_id, resources, deltas):
"""Check quotas and reserve resources for a tenant.
This method checks quotas against current usage,
reserved resources and the desired deltas.
If any of the proposed values is over the defined quota, an
QuotaExceeded exception will be raised with the sorted list of the
resources which are too high. Otherwise, the method returns a
list of reservation objects which were created.
:param tenant_id: The ID of the tenant reserving the resources.
:param resources: A dictionary of the registered resources.
:param deltas: A dictionary of the proposed delta changes.
"""
self.check_quotas(tenant_id, resources, deltas)
quota_usages = self.get_all_quota_usages_by_tenant(tenant_id,
deltas.keys())
reservations = []
for resource in sorted(deltas):
reserved = deltas[resource]
usage = quota_usages[resource]
usage.reserved += reserved
usage.save()
resv = Reservation.create(usage_id=usage.id,
delta=reserved,
status=Reservation.Statuses.RESERVED)
reservations.append(resv)
return reservations
def commit(self, reservations):
"""Commit reservations.
:param reservations: A list of the reservation UUIDs, as
returned by the reserve() method.
"""
for reservation in reservations:
usage = QuotaUsage.find_by(id=reservation.usage_id)
usage.in_use += reservation.delta
if usage.in_use < 0:
usage.in_use = 0
usage.reserved -= reservation.delta
reservation.status = Reservation.Statuses.COMMITTED
usage.save()
reservation.save()
def rollback(self, reservations):
"""Roll back reservations.
:param reservations: A list of the reservation UUIDs, as
returned by the reserve() method.
"""
for reservation in reservations:
usage = QuotaUsage.find_by(id=reservation.usage_id)
usage.reserved -= reservation.delta
reservation.status = Reservation.Statuses.ROLLEDBACK
usage.save()
reservation.save()
class QuotaEngine(object):
"""Represent the set of recognized quotas."""
def __init__(self, quota_driver_class=None):
"""Initialize a Quota object."""
self._resources = {}
if not quota_driver_class:
quota_driver_class = CONF.quota_driver
if isinstance(quota_driver_class, six.string_types):
quota_driver_class = importutils.import_object(quota_driver_class,
self._resources)
self._driver = quota_driver_class
def __contains__(self, resource):
return resource in self._resources
def register_resource(self, resource):
"""Register a resource."""
self._resources[resource.name] = resource
def register_resources(self, resources):
"""Register a dictionary of resources."""
for resource in resources:
self.register_resource(resource)
def get_quota_by_tenant(self, tenant_id, resource):
"""Get a specific quota by tenant."""
return self._driver.get_quota_by_tenant(tenant_id, resource)
def get_quota_usage(self, quota):
"""Get the usage for a quota."""
return self._driver.get_quota_usage_by_tenant(quota.tenant_id,
quota.resource)
def get_defaults(self):
"""Retrieve the default quotas."""
return self._driver.get_defaults(self._resources)
def get_all_quotas_by_tenant(self, tenant_id):
"""Retrieve the quotas for the given tenant.
:param tenant_id: The ID of the tenant to return quotas for.
"""
return self._driver.get_all_quotas_by_tenant(tenant_id,
self._resources)
def get_all_quota_usages_by_tenant(self, tenant_id):
"""Retrieve the quota usages for the given tenant.
:param tenant_id: The ID of the tenant to return quota usages for.
"""
return self._driver.get_all_quota_usages_by_tenant(tenant_id,
self._resources)
def check_quotas(self, tenant_id, **deltas):
self._driver.check_quotas(tenant_id, self._resources, deltas)
def reserve(self, tenant_id, **deltas):
"""Check quotas and reserve resources.
For counting quotas--those quotas for which there is a usage
synchronization function--this method checks quotas against
current usage and the desired deltas. The deltas are given as
keyword arguments, and current usage and other reservations
are factored into the quota check.
This method will raise a QuotaResourceUnknown exception if a
given resource is unknown or if it does not have a usage
synchronization function.
If any of the proposed values is over the defined quota, an
QuotaExceeded exception will be raised with the sorted list of the
resources which are too high. Otherwise, the method returns a
list of reservation UUIDs which were created.
:param tenant_id: The ID of the tenant to reserve quotas for.
"""
reservations = self._driver.reserve(tenant_id, self._resources, deltas)
LOG.debug("Created reservations %(reservations)s",
{'reservations': reservations})
return reservations
def commit(self, reservations):
"""Commit reservations.
:param reservations: A list of the reservation UUIDs, as
returned by the reserve() method.
"""
try:
self._driver.commit(reservations)
except Exception:
LOG.exception(_("Failed to commit reservations "
"%(reservations)s"), {'reservations': reservations})
def rollback(self, reservations):
"""Roll back reservations.
:param reservations: A list of the reservation UUIDs, as
returned by the reserve() method.
"""
try:
self._driver.rollback(reservations)
except Exception:
LOG.exception(_("Failed to roll back reservations "
"%(reservations)s"), {'reservations': reservations})
@property
def resources(self):
return sorted(self._resources.keys())
QUOTAS = QuotaEngine()
''' Define all kind of resources here '''
resources = [Resource(Resource.INSTANCES,
'max_instances_per_tenant'),
Resource(Resource.BACKUPS, 'max_backups_per_tenant'),
Resource(Resource.VOLUMES, 'max_volumes_per_tenant')]
QUOTAS.register_resources(resources)
def run_with_quotas(tenant_id, deltas, f):
"""Quota wrapper."""
reservations = QUOTAS.reserve(tenant_id, **deltas)
result = None
try:
result = f()
except Exception:
QUOTAS.rollback(reservations)
raise
else:
QUOTAS.commit(reservations)
return result
def check_quotas(tenant_id, deltas):
QUOTAS.check_quotas(tenant_id, **deltas)