e7db748c9e
1. Introduce quote usage related tables. 2. Add reserve() and commit() function to update quota usage in DB. 3. Invoke reserve() and commit() when users create or delete acclerators. Change-Id: I828bc6d35d08116a2b3c74baeda8876121541f8c
191 lines
7.7 KiB
Python
191 lines
7.7 KiB
Python
# Copyright 2018 Intel, Inc.
|
|
#
|
|
# 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 datetime
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import timeutils
|
|
import six
|
|
|
|
from cyborg import db as db_api
|
|
from cyborg.common import exception
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
quota_opts = [
|
|
cfg.IntOpt('reservation_expire',
|
|
default=86400,
|
|
help='Number of seconds until a reservation expires'),
|
|
cfg.IntOpt('until_refresh',
|
|
default=0,
|
|
help='Count of reservations until usage is refreshed'),
|
|
cfg.StrOpt('quota_driver',
|
|
default="cyborg.quota.DbQuotaDriver",
|
|
help='Default driver to use for quota checks'),
|
|
cfg.IntOpt('quota_fpgas',
|
|
default=10,
|
|
help='Total amount of fpga allowed per project'),
|
|
cfg.IntOpt('quota_gpus',
|
|
default=10,
|
|
help='Total amount of storage allowed per project'),
|
|
cfg.IntOpt('max_age',
|
|
default=0,
|
|
help='Number of seconds between subsequent usage refreshes')
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(quota_opts)
|
|
|
|
|
|
class QuotaEngine(object):
|
|
"""Represent the set of recognized quotas."""
|
|
|
|
def __init__(self, quota_driver_class=None):
|
|
"""Initialize a Quota object."""
|
|
|
|
self._resources = {}
|
|
self._driver = DbQuotaDriver()
|
|
|
|
def register_resource(self, resource):
|
|
"""Register a resource."""
|
|
self._resources[resource.name] = resource
|
|
|
|
def register_resources(self, resources):
|
|
"""Register a list of resources."""
|
|
for resource in resources:
|
|
self.register_resource(resource)
|
|
|
|
def reserve(self, context, deltas, expire=None, project_id=None):
|
|
"""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
|
|
OverQuota 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 context: The request context, for access checks.
|
|
:param expire: An optional parameter specifying an expiration
|
|
time for the reservations. If it is a simple
|
|
number, it is interpreted as a number of
|
|
seconds and added to the current time; if it is
|
|
a datetime.timedelta object, it will also be
|
|
added to the current time. A datetime.datetime
|
|
object will be interpreted as the absolute
|
|
expiration time. If None is specified, the
|
|
default expiration time set by
|
|
--default-reservation-expire will be used (this
|
|
value will be treated as a number of seconds).
|
|
:param project_id: Specify the project_id if current context
|
|
is admin and admin wants to impact on
|
|
common user's tenant.
|
|
"""
|
|
if not project_id:
|
|
project_id = context.project_id
|
|
reservations = self._driver.reserve(context, self._resources, deltas,
|
|
expire=expire,
|
|
project_id=project_id)
|
|
|
|
LOG.debug("Created reservations %s", reservations)
|
|
|
|
return reservations
|
|
|
|
def commit(self, context, reservations, project_id=None):
|
|
"""Commit reservations.
|
|
|
|
:param context: The request context, for access checks.
|
|
:param reservations: A list of the reservation UUIDs, as
|
|
returned by the reserve() method.
|
|
:param project_id: Specify the project_id if current context
|
|
is admin and admin wants to impact on
|
|
common user's tenant.
|
|
"""
|
|
project_id = context.project_id
|
|
try:
|
|
self._driver.commit(context, reservations, project_id=project_id)
|
|
except Exception:
|
|
# NOTE(Vek): Ignoring exceptions here is safe, because the
|
|
# usage resynchronization and the reservation expiration
|
|
# mechanisms will resolve the issue. The exception is
|
|
# logged, however, because this is less than optimal.
|
|
LOG.exception("Failed to commit reservations %s", reservations)
|
|
|
|
def rollback(self, context, reservations, project_id=None):
|
|
pass
|
|
|
|
|
|
class DbQuotaDriver(object):
|
|
"""Driver to perform check to enforcement of quotas.
|
|
|
|
Also allows to obtain quota information.
|
|
The default driver utilizes the local database.
|
|
"""
|
|
dbapi = db_api.get_instance()
|
|
|
|
def reserve(self, context, resources, deltas, expire=None,
|
|
project_id=None):
|
|
# Set up the reservation expiration
|
|
if expire is None:
|
|
expire = CONF.reservation_expire
|
|
if isinstance(expire, six.integer_types):
|
|
expire = datetime.timedelta(seconds=expire)
|
|
if isinstance(expire, datetime.timedelta):
|
|
expire = timeutils.utcnow() + expire
|
|
if not isinstance(expire, datetime.datetime):
|
|
raise exception.InvalidReservationExpiration(expire=expire)
|
|
|
|
# If project_id is None, then we use the project_id in context
|
|
if project_id is None:
|
|
project_id = context.project_id
|
|
|
|
return self._reserve(context, resources, deltas, expire,
|
|
project_id)
|
|
|
|
def _reserve(self, context, resources, deltas, expire, project_id):
|
|
return self.dbapi.quota_reserve(context, resources, deltas, expire,
|
|
CONF.until_refresh, CONF.max_age,
|
|
project_id=project_id)
|
|
|
|
def commit(self, context, reservations, project_id=None):
|
|
"""Commit reservations.
|
|
|
|
:param context: The request context, for access checks.
|
|
:param reservations: A list of the reservation UUIDs, as
|
|
returned by the reserve() method.
|
|
:param project_id: Specify the project_id if current context
|
|
is admin and admin wants to impact on
|
|
common user's tenant.
|
|
"""
|
|
|
|
try:
|
|
self.dbapi.reservation_commit(context, reservations,
|
|
project_id=project_id)
|
|
except Exception:
|
|
# NOTE(Vek): Ignoring exceptions here is safe, because the
|
|
# usage resynchronization and the reservation expiration
|
|
# mechanisms will resolve the issue. The exception is
|
|
# logged, however, because this is less than optimal.
|
|
LOG.exception("Failed to commit reservations %s", reservations)
|
|
|
|
QUOTAS = QuotaEngine()
|