Merge "Add tables and data modules for the quotas of Karbor"

This commit is contained in:
Zuul 2017-11-02 08:56:29 +00:00 committed by Gerrit Code Review
commit 6345644393
6 changed files with 981 additions and 1 deletions

View File

@ -675,3 +675,144 @@ def purge_deleted_rows(context, age_in_days):
:returns: number of deleted rows
"""
return IMPL.purge_deleted_rows(context, age_in_days=age_in_days)
####################
def quota_create(context, project_id, resource, limit):
"""Create a quota for the given project and resource."""
return IMPL.quota_create(context, project_id, resource, limit)
def quota_get(context, project_id, resource):
"""Retrieve a quota or raise if it does not exist."""
return IMPL.quota_get(context, project_id, resource)
def quota_get_all_by_project(context, project_id):
"""Retrieve all quotas associated with a given project."""
return IMPL.quota_get_all_by_project(context, project_id)
def quota_update(context, project_id, resource, limit):
"""Update a quota or raise if it does not exist."""
return IMPL.quota_update(context, project_id, resource, limit)
def quota_destroy(context, project_id, resource):
"""Destroy the quota or raise if it does not exist."""
return IMPL.quota_destroy(context, project_id, resource)
###################
def quota_class_create(context, class_name, resource, limit):
"""Create a quota class for the given name and resource."""
return IMPL.quota_class_create(context, class_name, resource, limit)
def quota_class_get(context, class_name, resource):
"""Retrieve a quota class or raise if it does not exist."""
return IMPL.quota_class_get(context, class_name, resource)
def quota_class_get_all_by_name(context, class_name):
"""Retrieve all quotas associated with a given quota class."""
return IMPL.quota_class_get_all_by_name(context, class_name)
def quota_class_update(context, class_name, resource, limit):
"""Update a quota class or raise if it does not exist."""
return IMPL.quota_class_update(context, class_name, resource, limit)
def quota_class_destroy(context, class_name, resource):
"""Destroy the quota class or raise if it does not exist."""
return IMPL.quota_class_destroy(context, class_name, resource)
def quota_class_destroy_all_by_name(context, class_name):
"""Destroy all quotas associated with a given quota class."""
return IMPL.quota_class_destroy_all_by_name(context, class_name)
###################
def quota_usage_create(context, project_id, resource, in_use, reserved,
until_refresh):
"""Create a quota usage for the given project and resource."""
return IMPL.quota_usage_create(context, project_id, resource,
in_use, reserved, until_refresh)
def quota_usage_get(context, project_id, resource):
"""Retrieve a quota usage or raise if it does not exist."""
return IMPL.quota_usage_get(context, project_id, resource)
def quota_usage_get_all_by_project(context, project_id):
"""Retrieve all usage associated with a given resource."""
return IMPL.quota_usage_get_all_by_project(context, project_id)
###################
def reservation_create(context, uuid, usage, project_id, resource, delta,
expire):
"""Create a reservation for the given project and resource."""
return IMPL.reservation_create(context, uuid, usage, project_id,
resource, delta, expire)
def reservation_get(context, uuid):
"""Retrieve a reservation or raise if it does not exist."""
return IMPL.reservation_get(context, uuid)
def reservation_get_all_by_project(context, project_id):
"""Retrieve all reservations associated with a given project."""
return IMPL.reservation_get_all_by_project(context, project_id)
def reservation_destroy(context, uuid):
"""Destroy the reservation or raise if it does not exist."""
return IMPL.reservation_destroy(context, uuid)
###################
def quota_reserve(context, resources, quotas, deltas, expire,
until_refresh, max_age, project_id=None):
"""Check quotas and create appropriate reservations."""
return IMPL.quota_reserve(context, resources, quotas, deltas, expire,
until_refresh, max_age, project_id=project_id)
def reservation_commit(context, reservations, project_id=None):
"""Commit quota reservations."""
return IMPL.reservation_commit(context, reservations,
project_id=project_id)
def reservation_rollback(context, reservations, project_id=None):
"""Roll back quota reservations."""
return IMPL.reservation_rollback(context, reservations,
project_id=project_id)
def quota_destroy_all_by_project(context, project_id):
"""Destroy all quotas associated with a given project."""
return IMPL.quota_destroy_all_by_project(context, project_id)
def reservation_expire(context):
"""Roll back any expired reservations."""
return IMPL.reservation_expire(context)
###################

View File

@ -19,6 +19,7 @@ import six
import sys
import threading
import time
import uuid
from oslo_config import cfg
from oslo_db import api as oslo_db_api
@ -1962,3 +1963,506 @@ def purge_deleted_rows(context, age_in_days):
rows_purged = result.rowcount
LOG.info("Deleted %(row)d rows from table=%(table)s",
{'row': rows_purged, 'table': table})
###################
@require_context
def quota_get(context, project_id, resource, session=None):
result = model_query(context, models.Quota, session=session,
read_deleted="no").\
filter_by(project_id=project_id).\
filter_by(resource=resource).\
first()
if not result:
raise exception.ProjectQuotaNotFound(project_id=project_id)
return result
@require_context
def quota_get_all_by_project(context, project_id):
authorize_project_context(context, project_id)
rows = model_query(context, models.Quota, read_deleted="no").\
filter_by(project_id=project_id).\
all()
result = {'project_id': project_id}
for row in rows:
result[row.resource] = row.hard_limit
return result
@require_admin_context
def quota_create(context, project_id, resource, limit):
quota_ref = models.Quota()
quota_ref.project_id = project_id
quota_ref.resource = resource
quota_ref.hard_limit = limit
session = get_session()
with session.begin():
quota_ref.save(session)
return quota_ref
@require_admin_context
def quota_update(context, project_id, resource, limit):
session = get_session()
with session.begin():
quota_ref = quota_get(context, project_id, resource, session=session)
quota_ref.hard_limit = limit
quota_ref.save(session=session)
@require_admin_context
def quota_destroy(context, project_id, resource):
session = get_session()
with session.begin():
quota_ref = quota_get(context, project_id, resource, session=session)
quota_ref.delete(session=session)
###################
@require_context
def quota_class_get(context, class_name, resource, session=None):
result = model_query(context, models.QuotaClass, session=session,
read_deleted="no").\
filter_by(class_name=class_name).\
filter_by(resource=resource).\
first()
if not result:
raise exception.QuotaClassNotFound(class_name=class_name)
return result
@require_context
def quota_class_get_all_by_name(context, class_name):
authorize_quota_class_context(context, class_name)
rows = model_query(context, models.QuotaClass, read_deleted="no").\
filter_by(class_name=class_name).\
all()
result = {'class_name': class_name}
for row in rows:
result[row.resource] = row.hard_limit
return result
def authorize_quota_class_context(context, class_name):
"""Ensures a request has permission to access the given quota class."""
if is_user_context(context):
if not context.quota_class:
raise exception.NotAuthorized()
elif context.quota_class != class_name:
raise exception.NotAuthorized()
@require_admin_context
def quota_class_create(context, class_name, resource, limit):
quota_class_ref = models.QuotaClass()
quota_class_ref.class_name = class_name
quota_class_ref.resource = resource
quota_class_ref.hard_limit = limit
session = get_session()
with session.begin():
quota_class_ref.save(session)
return quota_class_ref
@require_admin_context
def quota_class_update(context, class_name, resource, limit):
session = get_session()
with session.begin():
quota_class_ref = quota_class_get(context, class_name, resource,
session=session)
quota_class_ref.hard_limit = limit
quota_class_ref.save(session=session)
@require_admin_context
def quota_class_destroy(context, class_name, resource):
session = get_session()
with session.begin():
quota_class_ref = quota_class_get(context, class_name, resource,
session=session)
quota_class_ref.delete(session=session)
@require_admin_context
def quota_class_destroy_all_by_name(context, class_name):
session = get_session()
with session.begin():
quota_classes = model_query(context, models.QuotaClass,
session=session, read_deleted="no").\
filter_by(class_name=class_name).\
all()
for quota_class_ref in quota_classes:
quota_class_ref.delete(session=session)
###################
@require_context
def quota_usage_get(context, project_id, resource, session=None):
result = model_query(context, models.QuotaUsage, session=session,
read_deleted="no").\
filter_by(project_id=project_id).\
filter_by(resource=resource).\
first()
if not result:
raise exception.QuotaUsageNotFound(project_id=project_id)
return result
@require_context
def quota_usage_get_all_by_project(context, project_id):
authorize_project_context(context, project_id)
rows = model_query(context, models.QuotaUsage, read_deleted="no").\
filter_by(project_id=project_id).\
all()
result = {'project_id': project_id}
for row in rows:
result[row.resource] = dict(in_use=row.in_use, reserved=row.reserved)
return result
@require_admin_context
def quota_usage_create(context, project_id, resource, in_use, reserved,
until_refresh, session=None):
quota_usage_ref = models.QuotaUsage()
quota_usage_ref.project_id = project_id
quota_usage_ref.resource = resource
quota_usage_ref.in_use = in_use
quota_usage_ref.reserved = reserved
quota_usage_ref.until_refresh = until_refresh
if not session:
session = get_session()
with session.begin():
quota_usage_ref.save(session=session)
return quota_usage_ref
###################
@require_context
def reservation_get(context, uuid, session=None):
result = model_query(context, models.Reservation, session=session,
read_deleted="no").\
filter_by(uuid=uuid).first()
if not result:
raise exception.ReservationNotFound(uuid=uuid)
return result
@require_context
def reservation_get_all_by_project(context, project_id):
authorize_project_context(context, project_id)
rows = model_query(context, models.Reservation, read_deleted="no").\
filter_by(project_id=project_id).all()
result = {'project_id': project_id}
for row in rows:
result.setdefault(row.resource, {})
result[row.resource][row.uuid] = row.delta
return result
@require_admin_context
def reservation_create(context, uuid, usage, project_id, resource, delta,
expire, session=None):
reservation_ref = models.Reservation()
reservation_ref.uuid = uuid
reservation_ref.usage_id = usage['id']
reservation_ref.project_id = project_id
reservation_ref.resource = resource
reservation_ref.delta = delta
reservation_ref.expire = expire
if not session:
session = get_session()
with session.begin():
reservation_ref.save(session=session)
return reservation_ref
@require_admin_context
def reservation_destroy(context, uuid):
session = get_session()
with session.begin():
reservation_ref = reservation_get(context, uuid, session=session)
reservation_ref.delete(session=session)
###################
# NOTE(johannes): The quota code uses SQL locking to ensure races don't
# cause under or over counting of resources. To avoid deadlocks, this
# code always acquires the lock on quota_usages before acquiring the lock
# on reservations.
def _get_quota_usages(context, session, project_id):
# Broken out for testability
rows = model_query(context, models.QuotaUsage,
read_deleted="no",
session=session).\
filter_by(project_id=project_id).\
with_lockmode('update').\
all()
return dict((row.resource, row) for row in rows)
@require_context
def quota_reserve(context, resources, quotas, deltas, expire,
until_refresh, max_age, project_id=None):
elevated = context.elevated()
session = get_session()
with session.begin():
if project_id is None:
project_id = context.project_id
# Get the current usages
usages = _get_quota_usages(context, session, project_id)
# Handle usage refresh
work = set(deltas.keys())
while work:
resource = work.pop()
# Do we need to refresh the usage?
refresh = False
if resource not in usages:
usages[resource] = quota_usage_create(elevated,
project_id,
resource,
0, 0,
until_refresh or None,
session=session)
refresh = True
elif usages[resource].in_use < 0:
# Negative in_use count indicates a desync, so try to
# heal from that...
refresh = True
elif usages[resource].until_refresh is not None:
usages[resource].until_refresh -= 1
if usages[resource].until_refresh <= 0:
refresh = True
elif max_age and (usages[resource].updated_at -
timeutils.utcnow()).seconds >= max_age:
refresh = True
# OK, refresh the usage
if refresh:
# Grab the sync routine
sync = resources[resource].sync
updates = {}
if sync:
updates = sync(elevated, project_id, session)
for res, in_use in updates.items():
# Make sure we have a destination for the usage!
if res not in usages:
usages[res] = quota_usage_create(elevated,
project_id,
res,
0, 0,
until_refresh or None,
session=session)
# Update the usage
usages[res].in_use = in_use
usages[res].until_refresh = until_refresh or None
# Because more than one resource may be refreshed
# by the call to the sync routine, and we don't
# want to double-sync, we make sure all refreshed
# resources are dropped from the work set.
work.discard(res)
# NOTE(Vek): We make the assumption that the sync
# routine actually refreshes the
# resources that it is the sync routine
# for. We don't check, because this is
# a best-effort mechanism.
# Check for deltas that would go negative
unders = [res for res, delta in deltas.items()
if delta < 0 and
delta + usages[res].in_use < 0]
# Now, let's check the quotas
# NOTE(Vek): We're only concerned about positive increments.
# If a project has gone over quota, we want them to
# be able to reduce their usage without any
# problems.
overs = [res for res, delta in deltas.items()
if quotas[res] >= 0 and delta >= 0 and
quotas[res] < delta + usages[res].total]
# NOTE(Vek): The quota check needs to be in the transaction,
# but the transaction doesn't fail just because
# we're over quota, so the OverQuota raise is
# outside the transaction. If we did the raise
# here, our usage updates would be discarded, but
# they're not invalidated by being over-quota.
# Create the reservations
if not overs:
reservations = []
for resource, delta in deltas.items():
reservation = reservation_create(elevated,
str(uuid.uuid4()),
usages[resource],
project_id,
resource, delta, expire,
session=session)
reservations.append(reservation.uuid)
# Also update the reserved quantity
# NOTE(Vek): Again, we are only concerned here about
# positive increments. Here, though, we're
# worried about the following scenario:
#
# 1) User initiates resize down.
# 2) User allocates a new instance.
# 3) Resize down fails or is reverted.
# 4) User is now over quota.
#
# To prevent this, we only update the
# reserved value if the delta is positive.
if delta > 0:
usages[resource].reserved += delta
# Apply updates to the usages table
for usage_ref in usages.values():
usage_ref.save(session=session)
if unders:
LOG.warning(_("Change will make usage less than 0 for the following "
"resources: %(unders)s") % unders)
if overs:
usages = dict((k, dict(in_use=v['in_use'], reserved=v['reserved']))
for k, v in usages.items())
raise exception.OverQuota(overs=sorted(overs), quotas=quotas,
usages=usages)
return reservations
def _quota_reservations(session, context, reservations):
"""Return the relevant reservations."""
# Get the listed reservations
return model_query(context, models.Reservation,
read_deleted="no",
session=session).\
filter(models.Reservation.uuid.in_(reservations)).\
with_lockmode('update').\
all()
@require_context
def reservation_commit(context, reservations, project_id=None):
session = get_session()
with session.begin():
usages = _get_quota_usages(context, session, project_id)
for reservation in _quota_reservations(session, context, reservations):
usage = usages[reservation.resource]
if reservation.delta >= 0:
usage.reserved -= reservation.delta
usage.in_use += reservation.delta
reservation.delete(session=session)
for usage in usages.values():
usage.save(session=session)
@require_context
def reservation_rollback(context, reservations, project_id=None):
session = get_session()
with session.begin():
usages = _get_quota_usages(context, session, project_id)
for reservation in _quota_reservations(session, context, reservations):
usage = usages[reservation.resource]
if reservation.delta >= 0:
usage.reserved -= reservation.delta
reservation.delete(session=session)
for usage in usages.values():
usage.save(session=session)
@require_admin_context
def quota_destroy_all_by_project(context, project_id):
session = get_session()
with session.begin():
quotas = model_query(context, models.Quota, session=session,
read_deleted="no").\
filter_by(project_id=project_id).\
all()
for quota_ref in quotas:
quota_ref.delete(session=session)
quota_usages = model_query(context, models.QuotaUsage,
session=session, read_deleted="no").\
filter_by(project_id=project_id).\
all()
for quota_usage_ref in quota_usages:
quota_usage_ref.delete(session=session)
reservations = model_query(context, models.Reservation,
session=session, read_deleted="no").\
filter_by(project_id=project_id).\
all()
for reservation_ref in reservations:
reservation_ref.delete(session=session)
@require_admin_context
def reservation_expire(context):
session = get_session()
with session.begin():
current_time = timeutils.utcnow()
results = model_query(context, models.Reservation, session=session,
read_deleted="no").\
filter(models.Reservation.expire < current_time).\
all()
if results:
for reservation in results:
if reservation.delta >= 0:
reservation.usage.reserved -= reservation.delta
reservation.usage.save(session=session)
reservation.delete(session=session)
################

View File

@ -0,0 +1,84 @@
# 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 sqlalchemy import Boolean, Column, DateTime, Integer
from sqlalchemy import MetaData, String, Table, ForeignKey
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
quotas = Table(
'quotas', meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('deleted', Boolean),
Column('id', Integer, primary_key=True),
Column('project_id', String(length=255), nullable=False),
Column('resource', String(length=255), nullable=False),
Column('hard_limit', Integer),
mysql_engine='InnoDB'
)
quotas.create()
quota_classes = Table(
'quota_classes', meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('deleted', Boolean),
Column('id', Integer, primary_key=True),
Column('class_name', String(length=255), nullable=False),
Column('resource', String(length=255), nullable=False),
Column('hard_limit', Integer),
mysql_engine='InnoDB'
)
quota_classes.create()
quota_usages = Table(
'quota_usages', meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('deleted', Boolean),
Column('id', Integer, primary_key=True),
Column('project_id', String(length=255), nullable=False),
Column('resource', String(length=255), nullable=False),
Column('in_use', Integer),
Column('reserved', Integer),
Column('until_refresh', Integer, nullable=True),
mysql_engine='InnoDB'
)
quota_usages.create()
reservations = Table(
'reservations', meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('deleted', Boolean),
Column('id', Integer, primary_key=True),
Column('uuid', String(length=36), nullable=False),
Column('usage_id', Integer, ForeignKey('quota_usages.id'),
nullable=False),
Column('project_id', String(length=255), index=True),
Column('resource', String(length=255)),
Column('delta', Integer, nullable=False),
Column('expire', DateTime),
mysql_engine='InnoDB'
)
reservations.create()

View File

@ -247,6 +247,77 @@ class CheckpointRecord(BASE, KarborBase):
extend_info = Column(Text)
class Quota(BASE, KarborBase):
"""Represents a single quota override for a project.
If there is no row for a given project id and resource, then the
default for the quota class is used. If there is no row for a
given quota class and resource, then the default for the
deployment is used. If the row is present but the hard limit is
Null, then the resource is unlimited.
"""
__tablename__ = 'quotas'
id = Column(Integer, primary_key=True)
project_id = Column(String(255), index=True)
resource = Column(String(255))
hard_limit = Column(Integer, nullable=True)
class QuotaClass(BASE, KarborBase):
"""Represents a single quota override for a quota class.
If there is no row for a given quota class and resource, then the
default for the deployment is used. If the row is present but the
hard limit is Null, then the resource is unlimited.
"""
__tablename__ = 'quota_classes'
id = Column(Integer, primary_key=True)
class_name = Column(String(255), index=True)
resource = Column(String(255))
hard_limit = Column(Integer, nullable=True)
class QuotaUsage(BASE, KarborBase):
"""Represents the current usage for a given resource."""
__tablename__ = 'quota_usages'
id = Column(Integer, primary_key=True)
project_id = Column(String(255), index=True)
resource = Column(String(255))
in_use = Column(Integer)
reserved = Column(Integer)
@property
def total(self):
return self.in_use + self.reserved
until_refresh = Column(Integer, nullable=True)
class Reservation(BASE, KarborBase):
"""Represents a resource reservation for quotas."""
__tablename__ = 'reservations'
id = Column(Integer, primary_key=True)
uuid = Column(String(36), nullable=False)
usage_id = Column(Integer, ForeignKey('quota_usages.id'), nullable=False)
project_id = Column(String(255), index=True)
resource = Column(String(255))
delta = Column(Integer)
expire = Column(DateTime, nullable=False)
def register_models():
"""Register Models and create metadata.
@ -265,7 +336,11 @@ def register_models():
ScheduledOperationLog,
Restore,
Verification,
CheckpointRecord)
CheckpointRecord,
Quota,
QuotaClass,
QuotaUsage,
Reservation)
engine = create_engine(CONF.database.connection, echo=False)
for model in models:
model.metadata.create_all(engine)

View File

@ -393,3 +393,35 @@ class CheckpointNotBeDeleted(KarborException):
class GetProtectionNetworkSubResourceFailed(KarborException):
message = _("Get protection network sub-resources of type %(type)s failed:"
" %(reason)s")
class QuotaNotFound(NotFound):
message = _("Quota could not be found")
class QuotaResourceUnknown(QuotaNotFound):
message = _("Unknown quota resources %(unknown)s.")
class ProjectQuotaNotFound(QuotaNotFound):
message = _("Quota for project %(project_id)s could not be found.")
class QuotaClassNotFound(QuotaNotFound):
message = _("Quota class %(class_name)s could not be found.")
class QuotaUsageNotFound(QuotaNotFound):
message = _("Quota usage for project %(project_id)s could not be found.")
class ReservationNotFound(QuotaNotFound):
message = _("Quota reservation %(uuid)s could not be found.")
class OverQuota(KarborException):
message = _("Quota exceeded for resources: %(overs)s")
class InvalidReservationExpiration(Invalid):
message = _("Invalid reservation expiration %(expire)s.")

View File

@ -17,12 +17,15 @@ from datetime import timedelta
from oslo_config import cfg
from oslo_utils import uuidutils
import six
import uuid
from karbor import context
from karbor import db
from karbor import exception
from karbor.tests import base
from oslo_utils import timeutils
CONF = cfg.CONF
@ -707,3 +710,144 @@ class CheckpointRecordTestCase(ModelBaseTestCase):
self.assertRaises(exception.CheckpointRecordNotFound,
db.checkpoint_record_update,
self.ctxt, 42, {})
class QuotaDbTestCase(ModelBaseTestCase):
"""Unit tests for karbor.db.api.quota_*."""
def setUp(self):
super(QuotaDbTestCase, self).setUp()
self.ctxt = context.get_admin_context()
self.project_id = "586cc6ce-e286-40bd-b2b5-dd32694d9944"
self.resource = "volume_backups"
self.limit = 10
def test_quota_create(self):
quota = db.quota_create(self.ctxt, self.project_id,
self.resource, self.limit)
self.assertEqual("volume_backups", quota.resource)
db.quota_destroy(self.ctxt, self.project_id, self.resource)
def test_quota_get(self):
quota = db.quota_create(self.ctxt, self.project_id,
self.resource, self.limit)
self._assertEqualObjects(quota, db.quota_get(
self.ctxt, quota.project_id, quota.resource))
db.quota_destroy(self.ctxt, self.project_id, self.resource)
def test_quota_destroy(self):
quota = db.quota_create(self.ctxt, self.project_id,
self.resource, self.limit)
db.quota_destroy(self.ctxt, quota.project_id, quota.resource)
self.assertRaises(exception.ProjectQuotaNotFound, db.quota_get,
self.ctxt, quota.project_id, quota.resource)
def test_quota_update(self):
quota = db.quota_create(self.ctxt, self.project_id,
self.resource, self.limit)
db.quota_update(self.ctxt, quota.project_id, quota.resource,
20)
quota = db.quota_get(self.ctxt, self.project_id, self.resource)
self.assertEqual(20, quota.hard_limit)
class QuotaClassDbTestCase(ModelBaseTestCase):
"""Unit tests for karbor.db.api.quota_class_*."""
def setUp(self):
super(QuotaClassDbTestCase, self).setUp()
self.ctxt = context.get_admin_context()
self.class_name = "default"
self.resource = "volume_backups"
self.limit = 10
def test_quota_class_create(self):
quota_class = db.quota_class_create(self.ctxt, self.class_name,
self.resource, self.limit)
self.assertEqual("volume_backups", quota_class.resource)
db.quota_class_destroy(self.ctxt, self.class_name, self.resource)
def test_quota_class_get(self):
quota_class = db.quota_class_create(self.ctxt, self.class_name,
self.resource, self.limit)
self._assertEqualObjects(quota_class, db.quota_class_get(
self.ctxt, quota_class.class_name, quota_class.resource))
db.quota_class_destroy(self.ctxt, self.class_name, self.resource)
def test_quota_class_destroy(self):
quota_class = db.quota_class_create(self.ctxt, self.class_name,
self.resource, self.limit)
db.quota_class_destroy(self.ctxt, self.class_name, self.resource)
self.assertRaises(exception.QuotaClassNotFound, db.quota_class_get,
self.ctxt, quota_class.class_name,
quota_class.resource)
def test_quota_class_update(self):
quota_class = db.quota_class_create(self.ctxt, self.class_name,
self.resource, self.limit)
db.quota_class_update(self.ctxt, quota_class.class_name,
quota_class.resource, 20)
quota_class = db.quota_class_get(self.ctxt, self.class_name,
self.resource)
self.assertEqual(20, quota_class.hard_limit)
class QuotaUsageDbTestCase(ModelBaseTestCase):
"""Unit tests for karbor.db.api.quota_usage_*."""
def setUp(self):
super(QuotaUsageDbTestCase, self).setUp()
self.ctxt = context.get_admin_context()
self.project_id = "586cc6ce-e286-40bd-b2b5-dd32694d9944"
self.resource = "volume_backups"
self.in_use = 10
self.reserved = 10
self.until_refresh = 0
def test_quota_usage_create(self):
quota_usage = db.quota_usage_create(
self.ctxt, self.project_id, "volume_backups", self.in_use,
self.reserved, self.until_refresh)
self.assertEqual("volume_backups", quota_usage.resource)
def test_quota_usage_get(self):
quota_usage = db.quota_usage_create(
self.ctxt, self.project_id, "volume_backups_get", self.in_use,
self.reserved, self.until_refresh)
self._assertEqualObjects(quota_usage, db.quota_usage_get(
self.ctxt, self.project_id, "volume_backups_get"))
class ReservationDbTestCase(ModelBaseTestCase):
"""Unit tests for karbor.db.api.reservation_*."""
def setUp(self):
super(ReservationDbTestCase, self).setUp()
self.ctxt = context.get_admin_context()
self.project_id = "586cc6ce-e286-40bd-b2b5-dd32694d9944"
self.resource = "volume_backups"
self.in_use = 10
self.reserved = 10
self.until_refresh = 0
def test_reservation_create(self):
quota_usage = db.quota_usage_create(
self.ctxt, self.project_id, "volume_backups", self.in_use,
self.reserved, self.until_refresh)
reservation = db.reservation_create(
self.ctxt, str(uuid.uuid4()), quota_usage,
self.project_id, "volume_backups", 1,
timeutils.utcnow())
self.assertEqual("volume_backups", quota_usage.resource)
self.assertEqual("volume_backups", reservation.resource)
def test_reservation_get(self):
quota_usage = db.quota_usage_create(
self.ctxt, self.project_id, "volume_backups_get", self.in_use,
self.reserved, self.until_refresh)
reservation = db.reservation_create(
self.ctxt, str(uuid.uuid4()), quota_usage,
self.project_id, "volume_backups_get", 1,
timeutils.utcnow())
self._assertEqualObjects(reservation, db.reservation_get(
self.ctxt, reservation.uuid))