Remove part tables from Tricircle
1. What is the problem? Tricircle need remove some tables, we can see these tables in [1]. 2. What is the solution to the problem? This patch remove the follow tables: *quality_of_service_specs *quota_classes *quota_usages *quotas *reservations *volume_type_extra_specs *volume_type_projects *volume_types 3. What the features need to be implemented to the Tricircle to realize the solution? No new features. [1] https://blueprints.launchpad.net/tricircle/+spec/clean-legacy-tables Change-Id: Ied0db778365fc4c8002aeb81c647ce2aae521327
This commit is contained in:
parent
81387881a7
commit
3a85ac7d4a
|
@ -76,7 +76,7 @@ class ContextBase(oslo_ctx.RequestContext):
|
|||
def __init__(self, auth_token=None, user_id=None, tenant_id=None,
|
||||
is_admin=False, read_deleted="no", request_id=None,
|
||||
overwrite=True, user_name=None, tenant_name=None,
|
||||
quota_class=None, roles=None, **kwargs):
|
||||
roles=None, **kwargs):
|
||||
"""Initialize RequestContext.
|
||||
|
||||
:param read_deleted: 'no' indicates deleted records are hidden, 'yes'
|
||||
|
@ -101,7 +101,6 @@ class ContextBase(oslo_ctx.RequestContext):
|
|||
overwrite=overwrite)
|
||||
self.user_name = user_name
|
||||
self.tenant_name = tenant_name
|
||||
self.quota_class = quota_class
|
||||
self.read_deleted = read_deleted
|
||||
self.nova_micro_version = kwargs.get('nova_micro_version',
|
||||
constants.NOVA_APIGW_MIN_VERSION)
|
||||
|
@ -129,7 +128,6 @@ class ContextBase(oslo_ctx.RequestContext):
|
|||
'tenant_name': self.tenant_name,
|
||||
'tenant_id': self.tenant_id,
|
||||
'project_id': self.project_id,
|
||||
'quota_class': self.quota_class,
|
||||
'roles': self.roles,
|
||||
})
|
||||
return ctx_dict
|
||||
|
|
|
@ -183,72 +183,6 @@ class InvalidInput(Invalid):
|
|||
message = _("Invalid input received: %(reason)s")
|
||||
|
||||
|
||||
class InvalidMetadata(Invalid):
|
||||
message = _("Invalid metadata: %(reason)s")
|
||||
|
||||
|
||||
class InvalidMetadataSize(Invalid):
|
||||
message = _("Invalid metadata size: %(reason)s")
|
||||
|
||||
|
||||
class MetadataLimitExceeded(TricircleException):
|
||||
message = _("Maximum number of metadata items exceeds %(allowed)d")
|
||||
|
||||
|
||||
class InvalidReservationExpiration(Invalid):
|
||||
message = _("Invalid reservation expiration %(expire)s.")
|
||||
|
||||
|
||||
class InvalidQuotaValue(Invalid):
|
||||
message = _("Change would make usage less than 0 for the following "
|
||||
"resources: %(unders)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(TricircleException):
|
||||
message = _("Quota exceeded for resources: %(overs)s")
|
||||
|
||||
|
||||
class TooManyInstances(TricircleException):
|
||||
message = _("Quota exceeded for %(overs)s: Requested %(req)s,"
|
||||
" but already used %(used)s of %(allowed)s %(overs)s")
|
||||
|
||||
|
||||
class OnsetFileLimitExceeded(TricircleException):
|
||||
message = _("Personality file limit exceeded")
|
||||
|
||||
|
||||
class OnsetFilePathLimitExceeded(OnsetFileLimitExceeded):
|
||||
message = _("Personality file path too long")
|
||||
|
||||
|
||||
class OnsetFileContentLimitExceeded(OnsetFileLimitExceeded):
|
||||
message = _("Personality file content too long")
|
||||
|
||||
|
||||
class ExternalNetPodNotSpecify(TricircleException):
|
||||
message = "Pod for external network not specified"
|
||||
|
||||
|
@ -263,10 +197,6 @@ class PodNotFound(NotFound):
|
|||
super(PodNotFound, self).__init__(pod_name=pod_name)
|
||||
|
||||
|
||||
class ChildQuotaNotZero(TricircleException):
|
||||
message = _("Child projects having non-zero quota")
|
||||
|
||||
|
||||
# parameter validation error
|
||||
class ValidationError(TricircleException):
|
||||
message = _("%(msg)s")
|
||||
|
@ -279,32 +209,10 @@ class HTTPForbiddenError(TricircleException):
|
|||
code = 403
|
||||
|
||||
|
||||
class VolumeTypeNotFound(NotFound):
|
||||
message = _("Volume type %(volume_type_id)s could not be found.")
|
||||
|
||||
|
||||
class VolumeTypeNotFoundByName(VolumeTypeNotFound):
|
||||
message = _("Volume type with name %(volume_type_name)s "
|
||||
"could not be found.")
|
||||
|
||||
|
||||
class VolumeTypeExtraSpecsNotFound(NotFound):
|
||||
message = _("Volume Type %(volume_type_id)s has no extra specs with "
|
||||
"key %(extra_specs_key)s.")
|
||||
|
||||
|
||||
class Duplicate(TricircleException):
|
||||
pass
|
||||
|
||||
|
||||
class VolumeTypeExists(Duplicate):
|
||||
message = _("Volume Type %(id)s already exists.")
|
||||
|
||||
|
||||
class VolumeTypeUpdateFailed(TricircleException):
|
||||
message = _("Cannot update volume_type %(id)s")
|
||||
|
||||
|
||||
class ServerMappingsNotFound(NotFound):
|
||||
message = _('Instance %(server_id)s could not be found.')
|
||||
|
||||
|
|
|
@ -16,16 +16,11 @@
|
|||
import functools
|
||||
import sqlalchemy as sql
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_db import exception as db_exc
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
from sqlalchemy import or_, and_
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.sql.expression import literal_column
|
||||
|
||||
from tricircle.common import constants
|
||||
from tricircle.common.context import is_admin_context as _is_admin_context
|
||||
|
@ -399,9 +394,6 @@ def finish_job(context, job_id, successful, timestamp):
|
|||
core.update_resource(context, models.Job, job_id, job_dict)
|
||||
|
||||
|
||||
_DEFAULT_QUOTA_NAME = 'default'
|
||||
|
||||
|
||||
def _is_user_context(context):
|
||||
"""Indicates if the request context is a normal user."""
|
||||
if not context:
|
||||
|
@ -413,15 +405,6 @@ def _is_user_context(context):
|
|||
return True
|
||||
|
||||
|
||||
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 exceptions.NotAuthorized()
|
||||
elif context.quota_class != class_name:
|
||||
raise exceptions.NotAuthorized()
|
||||
|
||||
|
||||
def authorize_project_context(context, project_id):
|
||||
"""Ensures a request has permission to access the given project."""
|
||||
if _is_user_context(context):
|
||||
|
@ -538,627 +521,6 @@ def model_query(context, *args, **kwargs):
|
|||
return query
|
||||
|
||||
|
||||
@require_context
|
||||
def _quota_get(context, project_id, resource, session=None):
|
||||
result = model_query(context, models.Quotas, session=session,
|
||||
read_deleted="no").\
|
||||
filter_by(project_id=project_id).\
|
||||
filter_by(resource=resource).\
|
||||
first()
|
||||
|
||||
if not result:
|
||||
raise exceptions.ProjectQuotaNotFound(project_id=project_id)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
def quota_get(context, project_id, resource):
|
||||
return _quota_get(context, project_id, resource)
|
||||
|
||||
|
||||
@require_context
|
||||
def quota_get_all_by_project(context, project_id):
|
||||
authorize_project_context(context, project_id)
|
||||
|
||||
rows = model_query(context, models.Quotas, 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_context
|
||||
def quota_allocated_get_all_by_project(context, project_id):
|
||||
rows = model_query(context, models.Quotas, read_deleted='no').filter_by(
|
||||
project_id=project_id).all()
|
||||
result = {'project_id': project_id}
|
||||
for row in rows:
|
||||
result[row.resource] = row.allocated
|
||||
return result
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def quota_create(context, project_id, resource, limit, allocated=0):
|
||||
quota_ref = models.Quotas()
|
||||
quota_ref.project_id = project_id
|
||||
quota_ref.resource = resource
|
||||
quota_ref.hard_limit = limit
|
||||
if allocated:
|
||||
quota_ref.allocated = allocated
|
||||
|
||||
session = core.get_session()
|
||||
with session.begin():
|
||||
quota_ref.save(session)
|
||||
return quota_ref
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def quota_update(context, project_id, resource, limit):
|
||||
with context.session.begin():
|
||||
quota_ref = _quota_get(context, project_id, resource,
|
||||
session=context.session)
|
||||
quota_ref.hard_limit = limit
|
||||
return quota_ref
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def quota_allocated_update(context, project_id, resource, allocated):
|
||||
with context.session.begin():
|
||||
quota_ref = _quota_get(context, project_id, resource,
|
||||
session=context.session)
|
||||
quota_ref.allocated = allocated
|
||||
return quota_ref
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def quota_destroy(context, project_id, resource):
|
||||
with context.session.begin():
|
||||
quota_ref = _quota_get(context, project_id, resource,
|
||||
session=context.session)
|
||||
quota_ref.delete(session=context.session)
|
||||
|
||||
|
||||
@require_context
|
||||
def _quota_class_get(context, class_name, resource, session=None):
|
||||
result = model_query(context, models.QuotaClasses, session=session,
|
||||
read_deleted="no").\
|
||||
filter_by(class_name=class_name).\
|
||||
filter_by(resource=resource).\
|
||||
first()
|
||||
|
||||
if not result:
|
||||
raise exceptions.QuotaClassNotFound(class_name=class_name)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
def quota_class_get(context, class_name, resource):
|
||||
return _quota_class_get(context, class_name, resource)
|
||||
|
||||
|
||||
def quota_class_get_default(context):
|
||||
rows = model_query(context, models.QuotaClasses,
|
||||
read_deleted="no").\
|
||||
filter_by(class_name=_DEFAULT_QUOTA_NAME).all()
|
||||
|
||||
result = {'class_name': _DEFAULT_QUOTA_NAME}
|
||||
for row in rows:
|
||||
result[row.resource] = row.hard_limit
|
||||
|
||||
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.QuotaClasses, 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
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def quota_class_create(context, class_name, resource, limit):
|
||||
quota_class_ref = models.QuotaClasses()
|
||||
quota_class_ref.class_name = class_name
|
||||
quota_class_ref.resource = resource
|
||||
quota_class_ref.hard_limit = limit
|
||||
|
||||
session = core.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):
|
||||
with context.session.begin():
|
||||
quota_class_ref = _quota_class_get(context, class_name, resource,
|
||||
session=context.session)
|
||||
quota_class_ref.hard_limit = limit
|
||||
|
||||
return quota_class_ref
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def quota_class_destroy(context, class_name, resource):
|
||||
with context.session.begin():
|
||||
quota_class_ref = _quota_class_get(context, class_name, resource,
|
||||
session=context.session)
|
||||
quota_class_ref.delete(session=context.session)
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def quota_class_destroy_all_by_name(context, class_name):
|
||||
with context.session.begin():
|
||||
quota_classes = model_query(context, models.QuotaClasses,
|
||||
session=context.session,
|
||||
read_deleted="no").\
|
||||
filter_by(class_name=class_name).\
|
||||
all()
|
||||
|
||||
for quota_class_ref in quota_classes:
|
||||
quota_class_ref.delete(session=context.session)
|
||||
|
||||
|
||||
@require_context
|
||||
def quota_usage_get(context, project_id, resource):
|
||||
result = model_query(context, models.QuotaUsages, read_deleted="no").\
|
||||
filter_by(project_id=project_id).\
|
||||
filter_by(resource=resource).\
|
||||
first()
|
||||
|
||||
if not result:
|
||||
raise exceptions.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.QuotaUsages, 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.QuotaUsages()
|
||||
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
|
||||
quota_usage_ref.save(session=session)
|
||||
|
||||
return quota_usage_ref
|
||||
|
||||
|
||||
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
|
||||
reservation_ref.save(session=session)
|
||||
|
||||
return reservation_ref
|
||||
|
||||
|
||||
# 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.QuotaUsages,
|
||||
read_deleted="no",
|
||||
session=session).\
|
||||
filter_by(project_id=project_id).\
|
||||
with_lockmode('update').\
|
||||
all()
|
||||
return {row.resource: row for row in rows}
|
||||
|
||||
|
||||
def _get_quota_usages_by_resource(context, session, project_id, resource):
|
||||
# TODO(joehuang), add user_id as part of the filter
|
||||
rows = model_query(context, models.QuotaUsages,
|
||||
read_deleted="no",
|
||||
session=session).\
|
||||
filter_by(project_id=project_id).\
|
||||
filter_by(resource=resource).\
|
||||
with_lockmode('update').\
|
||||
all()
|
||||
return {row.resource: row for row in rows}
|
||||
|
||||
|
||||
@require_context
|
||||
@_retry_on_deadlock
|
||||
def quota_reserve(context, resources, quotas, deltas, expire,
|
||||
until_refresh, max_age, project_id=None):
|
||||
elevated = context.elevated()
|
||||
with context.session.begin():
|
||||
if project_id is None:
|
||||
project_id = context.project_id
|
||||
|
||||
# Get the current usages
|
||||
usages = _get_quota_usages(context, context.session, project_id)
|
||||
|
||||
# Handle usage refresh
|
||||
refresh = False
|
||||
work = set(deltas.keys())
|
||||
while work:
|
||||
resource = work.pop()
|
||||
|
||||
# Do we need to refresh the usage?
|
||||
if resource not in usages:
|
||||
usages[resource] = _quota_usage_create(elevated,
|
||||
project_id,
|
||||
resource,
|
||||
0, 0,
|
||||
until_refresh or None,
|
||||
session=context.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 is not None and (
|
||||
(usages[resource].updated_at -
|
||||
timeutils.utcnow()).seconds >= max_age):
|
||||
refresh = True
|
||||
|
||||
if refresh:
|
||||
# no actural usage refresh here
|
||||
|
||||
# refresh from the bottom pod
|
||||
usages[resource].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(resource)
|
||||
|
||||
# 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 = [r for r, delta in deltas.items()
|
||||
if delta < 0 and delta + usages[r].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 = [r for r, delta in deltas.items()
|
||||
if quotas[r] >= 0 and delta >= 0 and
|
||||
quotas[r] < delta + usages[r].in_use + usages[r].reserved]
|
||||
|
||||
# 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=context.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
|
||||
|
||||
if unders:
|
||||
LOG.warning(_LW("Change will make usage less than 0 for the following "
|
||||
"resources: %s"), unders)
|
||||
if overs:
|
||||
usages = {k: dict(in_use=v['in_use'], reserved=v['reserved'])
|
||||
for k, v in usages.items()}
|
||||
raise exceptions.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
|
||||
@_retry_on_deadlock
|
||||
def reservation_commit(context, reservations, project_id=None):
|
||||
with context.session.begin():
|
||||
usages = _get_quota_usages(context, context.session, project_id)
|
||||
|
||||
for reservation in _quota_reservations(context.session,
|
||||
context,
|
||||
reservations):
|
||||
usage = usages[reservation.resource]
|
||||
if reservation.delta >= 0:
|
||||
usage.reserved -= reservation.delta
|
||||
usage.in_use += reservation.delta
|
||||
|
||||
reservation.delete(session=context.session)
|
||||
|
||||
|
||||
@require_context
|
||||
@_retry_on_deadlock
|
||||
def reservation_rollback(context, reservations, project_id=None):
|
||||
with context.session.begin():
|
||||
usages = _get_quota_usages(context, context.session, project_id)
|
||||
|
||||
for reservation in _quota_reservations(context.session,
|
||||
context,
|
||||
reservations):
|
||||
usage = usages[reservation.resource]
|
||||
if reservation.delta >= 0:
|
||||
usage.reserved -= reservation.delta
|
||||
|
||||
reservation.delete(session=context.session)
|
||||
|
||||
|
||||
def quota_destroy_by_project(*args, **kwargs):
|
||||
"""Destroy all limit quotas associated with a project.
|
||||
|
||||
Leaves usage and reservation quotas intact.
|
||||
"""
|
||||
quota_destroy_all_by_project(only_quotas=True, *args, **kwargs)
|
||||
|
||||
|
||||
@require_admin_context
|
||||
@_retry_on_deadlock
|
||||
def quota_destroy_all_by_project(context, project_id, only_quotas=False):
|
||||
"""Destroy all quotas associated with a project.
|
||||
|
||||
This includes limit quotas, usage quotas and reservation quotas.
|
||||
Optionally can only remove limit quotas and leave other types as they are.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
:param project_id: The ID of the project being deleted.
|
||||
:param only_quotas: Only delete limit quotas, leave other types intact.
|
||||
"""
|
||||
with context.session.begin():
|
||||
quotas = model_query(context, models.Quotas, session=context.session,
|
||||
read_deleted="no").\
|
||||
filter_by(project_id=project_id).\
|
||||
all()
|
||||
|
||||
for quota_ref in quotas:
|
||||
quota_ref.delete(session=context.session)
|
||||
|
||||
if only_quotas:
|
||||
return
|
||||
|
||||
quota_usages = model_query(context, models.QuotaUsages,
|
||||
session=context.session,
|
||||
read_deleted="no").\
|
||||
filter_by(project_id=project_id).\
|
||||
all()
|
||||
|
||||
for quota_usage_ref in quota_usages:
|
||||
quota_usage_ref.delete(session=context.session)
|
||||
|
||||
reservations = model_query(context, models.Reservation,
|
||||
session=context.session,
|
||||
read_deleted="no").\
|
||||
filter_by(project_id=project_id).\
|
||||
all()
|
||||
|
||||
for reservation_ref in reservations:
|
||||
reservation_ref.delete(session=context.session)
|
||||
|
||||
|
||||
@require_admin_context
|
||||
@_retry_on_deadlock
|
||||
def reservation_expire(context):
|
||||
with context.session.begin():
|
||||
current_time = timeutils.utcnow()
|
||||
results = model_query(context, models.Reservation,
|
||||
session=context.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=context.session)
|
||||
|
||||
reservation.delete(session=context.session)
|
||||
|
||||
|
||||
def _dict_with_extra_specs_if_authorized(context, inst_type_query):
|
||||
"""Convert type query result to dict with extra_spec and rate_limit.
|
||||
|
||||
Takes a volume type query returned by sqlalchemy and returns it
|
||||
as a dictionary, converting the extra_specs entry from a list
|
||||
of dicts.
|
||||
|
||||
NOTE:
|
||||
the contents of extra-specs are admin readable only.
|
||||
If the context passed in for this request is not about admin,
|
||||
we will return an empty extra-specs dict rather than
|
||||
providing extra-specs details.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
:param inst_type_query: list of extra-specs.
|
||||
:returns dictionary of extra-specs.
|
||||
|
||||
Example of response of admin context:
|
||||
|
||||
'extra_specs' : [{'key': 'k1', 'value': 'v1', ...}, ...]
|
||||
to a single dict:
|
||||
'extra_specs' : {'k1': 'v1'}
|
||||
|
||||
"""
|
||||
|
||||
inst_type_dict = dict(inst_type_query)
|
||||
if not context.is_admin:
|
||||
del (inst_type_dict['extra_specs'])
|
||||
else:
|
||||
extra_specs = {x['key']: x['value']
|
||||
for x in inst_type_query['extra_specs']}
|
||||
inst_type_dict['extra_specs'] = extra_specs
|
||||
return inst_type_dict
|
||||
|
||||
|
||||
@require_context
|
||||
def _volume_type_get_by_name(context, name, session=None):
|
||||
result = model_query(context, models.VolumeTypes, session=session). \
|
||||
options(joinedload('extra_specs')). \
|
||||
filter_by(name=name). \
|
||||
first()
|
||||
|
||||
if not result:
|
||||
raise exceptions.VolumeTypeNotFoundByName(volume_type_name=name)
|
||||
|
||||
return _dict_with_extra_specs_if_authorized(context, result)
|
||||
|
||||
|
||||
@require_context
|
||||
def volume_type_get_by_name(context, name, session=None):
|
||||
"""Return a dict describing specific volume_type.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
:param name: The name of volume type to be found.
|
||||
:returns Volume type.
|
||||
"""
|
||||
return _volume_type_get_by_name(context, name, session)
|
||||
|
||||
|
||||
def _volume_type_get_query(context, session=None, read_deleted='no'):
|
||||
query = model_query(context, models.VolumeTypes,
|
||||
session=session,
|
||||
read_deleted=read_deleted). \
|
||||
options(joinedload('extra_specs'))
|
||||
|
||||
if not context.is_admin:
|
||||
is_public = True
|
||||
the_filter = [models.VolumeTypes.is_public == is_public]
|
||||
query.filter(or_(*the_filter))
|
||||
|
||||
return query
|
||||
|
||||
|
||||
def _volume_type_get_db_object(context, id, session=None, inactive=False):
|
||||
read_deleted = "yes" if inactive else "no"
|
||||
result = _volume_type_get_query(
|
||||
context, session, read_deleted). \
|
||||
filter_by(id=id). \
|
||||
first()
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
def _volume_type_get(context, id, session=None, inactive=False):
|
||||
result = _volume_type_get_db_object(context, id, session, inactive)
|
||||
|
||||
if not result:
|
||||
raise exceptions.VolumeTypeNotFound(volume_type_id=id)
|
||||
|
||||
vtype = _dict_with_extra_specs_if_authorized(context, result)
|
||||
|
||||
return vtype
|
||||
|
||||
|
||||
@require_context
|
||||
def volume_type_get(context, id, inactive=False):
|
||||
"""Return a dict describing specific volume_type.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
:param id: The id of volume type to be found.
|
||||
:returns Volume type.
|
||||
"""
|
||||
|
||||
return _volume_type_get(context, id,
|
||||
session=None,
|
||||
inactive=inactive)
|
||||
|
||||
|
||||
@require_context
|
||||
def volume_type_delete(context, id, session):
|
||||
"""delete a volume_type by id.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
:param id: The id of volume type to be deleted.
|
||||
"""
|
||||
model_query(context, models.VolumeTypes, session=session, read_deleted="no").\
|
||||
filter_by(id=id). \
|
||||
update({'deleted': True,
|
||||
'deleted_at': timeutils.utcnow(),
|
||||
'updated_at': literal_column('updated_at')})
|
||||
|
||||
model_query(context, models.VolumeTypeExtraSpecs, session=session, read_deleted="no"). \
|
||||
filter_by(volume_type_id=id). \
|
||||
update({'deleted': True,
|
||||
'deleted_at': timeutils.utcnow(),
|
||||
'updated_at': literal_column('updated_at')})
|
||||
|
||||
|
||||
def is_valid_model_filters(model, filters):
|
||||
"""Return True if filter values exist on the model
|
||||
|
||||
|
@ -1169,198 +531,3 @@ def is_valid_model_filters(model, filters):
|
|||
if not hasattr(model, key):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _process_volume_types_filters(query, filters):
|
||||
context = filters.pop('context', None)
|
||||
|
||||
if filters.get('is_public'):
|
||||
the_filter = [models.VolumeTypes.is_public == filters['is_public']]
|
||||
|
||||
if filters['is_public'] and context.project_id is not None:
|
||||
projects_attr = getattr(models.VolumeTypes, 'projects')
|
||||
the_filter.append(
|
||||
[projects_attr.any(project_id=context.project_id,
|
||||
deleted=0)])
|
||||
|
||||
if len(the_filter) > 1:
|
||||
query = query.filter(or_(*the_filter))
|
||||
else:
|
||||
query = query.filter(the_filter[0])
|
||||
|
||||
if 'is_public' in filters:
|
||||
del filters['is_public']
|
||||
|
||||
if filters:
|
||||
# Ensure that filters' keys exist on the model
|
||||
if not is_valid_model_filters(models.VolumeTypes, filters):
|
||||
return
|
||||
|
||||
if filters.get('extra_specs') is not None:
|
||||
the_filter = []
|
||||
searchdict = filters.get('extra_specs')
|
||||
extra_specs = getattr(models.VolumeTypes, 'extra_specs')
|
||||
for k, v in searchdict.items():
|
||||
the_filter.append([extra_specs.any(key=k, value=v,
|
||||
deleted=False)])
|
||||
|
||||
if len(the_filter) > 1:
|
||||
query = query.filter(and_(*the_filter))
|
||||
else:
|
||||
query = query.filter(the_filter[0])
|
||||
del filters['extra_specs']
|
||||
query = query.filter_by(**filters)
|
||||
return query
|
||||
|
||||
|
||||
@require_context
|
||||
def volume_type_get_all(context, inactive=False, filters=None,
|
||||
list_result=False):
|
||||
"""Returns a dict describing all volume_types with name as key.
|
||||
|
||||
:param context: context to query under
|
||||
:param inactive: Pass true as argument if you want deleted volume types
|
||||
returned also.
|
||||
:param filters: dictionary of filters; values that are in lists, tuples,
|
||||
or sets cause an 'IN' operation, while exact matching
|
||||
is used for other values, see _process_volume_type_filters
|
||||
function for more information
|
||||
:param list_result: For compatibility, if list_result = True, return
|
||||
a list instead of dict.
|
||||
:returns: list/dict of matching volume types
|
||||
"""
|
||||
read_deleted = 'yes' if inactive else 'no'
|
||||
session = core.get_session()
|
||||
with session.begin():
|
||||
filters = filters or {}
|
||||
filters['context'] = context
|
||||
# Generate the query
|
||||
query = _volume_type_get_query(context, session=session,
|
||||
read_deleted=read_deleted)
|
||||
query = _process_volume_types_filters(query, filters)
|
||||
|
||||
# No volume types would match, return empty dict or list
|
||||
if query is None:
|
||||
if list_result:
|
||||
return []
|
||||
return {}
|
||||
|
||||
rows = query.all()
|
||||
|
||||
if list_result:
|
||||
result = [_dict_with_extra_specs_if_authorized(context, row)
|
||||
for row in rows]
|
||||
return result
|
||||
result = {row['name']: _dict_with_extra_specs_if_authorized(context,
|
||||
row)
|
||||
for row in rows}
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
def _volume_type_ref_get(context, id, session=None, inactive=False):
|
||||
read_deleted = "yes" if inactive else "no"
|
||||
result = model_query(context,
|
||||
models.VolumeTypes,
|
||||
session=session,
|
||||
read_deleted=read_deleted).\
|
||||
options(joinedload('extra_specs')).\
|
||||
filter_by(id=id).\
|
||||
first()
|
||||
|
||||
if not result:
|
||||
raise exceptions.VolumeTypeNotFound(volume_type_id=id)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@handle_db_data_error
|
||||
@require_admin_context
|
||||
def volume_type_update(context, volume_type_id, values):
|
||||
"""Update volume type by volume_type_id.
|
||||
|
||||
:param volume_type_id: id of volume type to be updated
|
||||
:param values: dictionary of values to be updated
|
||||
:returns: updated volume type
|
||||
"""
|
||||
session = core.get_session()
|
||||
with session.begin():
|
||||
try:
|
||||
# Check it exists
|
||||
volume_type_ref = _volume_type_ref_get(context,
|
||||
volume_type_id,
|
||||
session)
|
||||
if not volume_type_ref:
|
||||
raise exceptions.VolumeTypeNotFound(type_id=volume_type_id)
|
||||
|
||||
# No description change
|
||||
if values['description'] is None:
|
||||
del values['description']
|
||||
|
||||
# No is_public change
|
||||
if values['is_public'] is None:
|
||||
del values['is_public']
|
||||
|
||||
# No name change
|
||||
if values['name'] is None:
|
||||
del values['name']
|
||||
else:
|
||||
# Volume type name is unique. If change to a name that
|
||||
# belongs to a different volume_type , it should be
|
||||
# prevented.
|
||||
check_vol_type = None
|
||||
try:
|
||||
check_vol_type = \
|
||||
volume_type_get_by_name(context,
|
||||
values['name'],
|
||||
session=session)
|
||||
except exceptions.VolumeTypeNotFoundByName:
|
||||
pass
|
||||
else:
|
||||
if check_vol_type.get('id') != volume_type_id:
|
||||
raise exceptions.VolumeTypeExists(id=values['name'])
|
||||
|
||||
volume_type_ref.update(values)
|
||||
volume_type_ref.save(session=session)
|
||||
except Exception:
|
||||
raise exceptions.VolumeTypeUpdateFailed(id=volume_type_id)
|
||||
|
||||
return _dict_with_extra_specs_if_authorized(context, volume_type_ref)
|
||||
|
||||
|
||||
@require_context
|
||||
def volume_type_project_query(context, session=None, inactive=False,
|
||||
filters=None):
|
||||
"""Get a query of volume type project.
|
||||
|
||||
:param context: context to query under
|
||||
:param inactive: Pass true as argument if you want deleted
|
||||
volume type projects returned also.
|
||||
:param filters: dictionary of filters.
|
||||
"""
|
||||
read_deleted = "yes" if inactive else "no"
|
||||
filters = filters or {}
|
||||
return model_query(context, models.VolumeTypeProjects, session=session,
|
||||
read_deleted=read_deleted).filter_by(**filters)
|
||||
|
||||
|
||||
@require_context
|
||||
def get_server_mappings_by_top_id(context, server_id):
|
||||
server_mappings = \
|
||||
get_bottom_mappings_by_top_id(context, server_id, constants.RT_SERVER)
|
||||
|
||||
if not server_mappings:
|
||||
raise exceptions.ServerMappingsNotFound(server_id=server_id)
|
||||
|
||||
return server_mappings
|
||||
|
||||
|
||||
@require_context
|
||||
def get_volume_mappings_by_top_id(context, volume_id):
|
||||
volume_mappings = \
|
||||
get_bottom_mappings_by_top_id(context, volume_id, constants.RT_VOLUME)
|
||||
|
||||
if not volume_mappings:
|
||||
raise exceptions.VolumeMappingsNotFound(volume_id=volume_id)
|
||||
|
||||
return volume_mappings
|
||||
|
|
|
@ -115,130 +115,6 @@ def upgrade(migrate_engine):
|
|||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
quotas = sql.Table(
|
||||
'quotas', meta,
|
||||
sql.Column('id', sql.Integer, primary_key=True),
|
||||
sql.Column('project_id', sql.String(255), index=True),
|
||||
sql.Column('resource', sql.String(255), nullable=False),
|
||||
sql.Column('hard_limit', sql.Integer),
|
||||
sql.Column('allocated', sql.Integer, default=0),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
sql.Column('deleted_at', sql.DateTime),
|
||||
sql.Column('deleted', sql.Integer),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
quota_classes = sql.Table(
|
||||
'quota_classes', meta,
|
||||
sql.Column('id', sql.Integer, primary_key=True),
|
||||
sql.Column('class_name', sql.String(255), index=True),
|
||||
sql.Column('resource', sql.String(255), nullable=False),
|
||||
sql.Column('hard_limit', sql.Integer),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
sql.Column('deleted_at', sql.DateTime),
|
||||
sql.Column('deleted', sql.Integer),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
quota_usages = sql.Table(
|
||||
'quota_usages', meta,
|
||||
sql.Column('id', sql.Integer, primary_key=True),
|
||||
sql.Column('project_id', sql.String(255), index=True),
|
||||
sql.Column('user_id', sql.String(255), index=True),
|
||||
sql.Column('resource', sql.String(255), nullable=False),
|
||||
sql.Column('in_use', sql.Integer),
|
||||
sql.Column('reserved', sql.Integer),
|
||||
sql.Column('until_refresh', sql.Integer),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
sql.Column('deleted_at', sql.DateTime),
|
||||
sql.Column('deleted', sql.Integer),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
reservations = sql.Table(
|
||||
'reservations', meta,
|
||||
sql.Column('id', sql.Integer(), primary_key=True),
|
||||
sql.Column('uuid', sql.String(length=36), nullable=False),
|
||||
sql.Column('usage_id', sql.Integer(),
|
||||
sql.ForeignKey('quota_usages.id'),
|
||||
nullable=False),
|
||||
sql.Column('project_id',
|
||||
sql.String(length=255),
|
||||
index=True),
|
||||
sql.Column('resource',
|
||||
sql.String(length=255)),
|
||||
sql.Column('delta', sql.Integer(), nullable=False),
|
||||
sql.Column('expire', sql.DateTime),
|
||||
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
sql.Column('deleted_at', sql.DateTime),
|
||||
sql.Column('deleted', sql.Boolean(create_constraint=True,
|
||||
name=None)),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
volume_types = sql.Table(
|
||||
'volume_types', meta,
|
||||
sql.Column('id', sql.String(36), primary_key=True),
|
||||
sql.Column('name', sql.String(255), unique=True),
|
||||
sql.Column('description', sql.String(255)),
|
||||
sql.Column('qos_specs_id', sql.String(36)),
|
||||
sql.Column('is_public', sql.Boolean, default=True),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
sql.Column('deleted_at', sql.DateTime),
|
||||
sql.Column('deleted', sql.Boolean),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
volume_type_extra_specs = sql.Table(
|
||||
'volume_type_extra_specs', meta,
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
sql.Column('deleted_at', sql.DateTime),
|
||||
sql.Column('deleted', sql.Boolean),
|
||||
sql.Column('id', sql.Integer, primary_key=True, nullable=False),
|
||||
sql.Column('volume_type_id', sql.String(36),
|
||||
sql.ForeignKey('volume_types.id'),
|
||||
nullable=False),
|
||||
sql.Column('key', sql.String(length=255)),
|
||||
sql.Column('value', sql.String(length=255)),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
volume_type_projects = sql.Table(
|
||||
'volume_type_projects', meta,
|
||||
sql.Column('id', sql.Integer, primary_key=True, nullable=False),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
sql.Column('deleted_at', sql.DateTime),
|
||||
sql.Column('volume_type_id', sql.String(36),
|
||||
sql.ForeignKey('volume_types.id'),
|
||||
nullable=False),
|
||||
sql.Column('project_id', sql.String(length=255)),
|
||||
sql.Column('deleted', sql.Boolean(create_constraint=True, name=None)),
|
||||
migrate.UniqueConstraint(
|
||||
'volume_type_id', 'project_id', 'deleted',
|
||||
name='uniq_volume_type_projects0volume_type_id0project_id0deleted'
|
||||
),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
quality_of_service_specs = sql.Table(
|
||||
'quality_of_service_specs', meta,
|
||||
sql.Column('id', sql.String(36), primary_key=True),
|
||||
sql.Column('specs_id', sql.String(36)),
|
||||
sql.Column('key', sql.String(255)),
|
||||
sql.Column('value', sql.String(255)),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
cascaded_pods_resource_routing = sql.Table(
|
||||
'cascaded_pods_resource_routing', meta,
|
||||
sql.Column('id', sql.BigInteger, primary_key=True),
|
||||
|
@ -273,9 +149,7 @@ def upgrade(migrate_engine):
|
|||
|
||||
tables = [aggregates, aggregate_metadata, instance_types,
|
||||
instance_type_projects, instance_type_extra_specs, key_pairs,
|
||||
quotas, quota_classes, quota_usages, reservations,
|
||||
volume_types, volume_type_extra_specs, volume_type_projects,
|
||||
job, quality_of_service_specs, cascaded_pods_resource_routing]
|
||||
job, cascaded_pods_resource_routing]
|
||||
for table in tables:
|
||||
table.create()
|
||||
|
||||
|
@ -285,16 +159,6 @@ def upgrade(migrate_engine):
|
|||
'references': [instance_types.c.id]},
|
||||
{'columns': [instance_type_extra_specs.c.instance_type_id],
|
||||
'references': [instance_types.c.id]},
|
||||
{'columns': [reservations.c.usage_id],
|
||||
'references': [quota_usages.c.id]},
|
||||
{'columns': [volume_types.c.qos_specs_id],
|
||||
'references': [quality_of_service_specs.c.id]},
|
||||
{'columns': [volume_type_extra_specs.c.volume_type_id],
|
||||
'references': [volume_types.c.id]},
|
||||
{'columns': [volume_type_projects.c.volume_type_id],
|
||||
'references': [volume_types.c.id]},
|
||||
{'columns': [quality_of_service_specs.c.specs_id],
|
||||
'references': [quality_of_service_specs.c.id]},
|
||||
{'columns': [aggregate_metadata.c.aggregate_id],
|
||||
'references': [aggregates.c.id]},
|
||||
{'columns': [cascaded_pods_resource_routing.c.pod_id],
|
||||
|
|
|
@ -14,11 +14,9 @@
|
|||
# under the License.
|
||||
|
||||
from oslo_db.sqlalchemy import models
|
||||
from oslo_utils import timeutils
|
||||
|
||||
import sqlalchemy as sql
|
||||
from sqlalchemy.dialects import mysql
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import schema
|
||||
|
||||
from tricircle.db import core
|
||||
|
@ -145,244 +143,6 @@ class KeyPair(core.ModelBase, core.DictBase, models.TimestampMixin):
|
|||
nullable=False, server_default='ssh')
|
||||
|
||||
|
||||
# Quota part are ported from Cinder for hierarchy multi-tenancy quota control
|
||||
class QuotasBase(models.ModelBase, core.DictBase,
|
||||
models.TimestampMixin, models.SoftDeleteMixin):
|
||||
"""QuotasBase.
|
||||
|
||||
provide base class for quota series tables. For it inherits from
|
||||
models.ModelBase, this is different from other tables
|
||||
"""
|
||||
__table_args__ = {'mysql_engine': 'InnoDB'}
|
||||
|
||||
metadata = None
|
||||
|
||||
def delete(self, session):
|
||||
"""Delete this object."""
|
||||
self.deleted = True
|
||||
self.deleted_at = timeutils.utcnow()
|
||||
self.save(session=session)
|
||||
|
||||
|
||||
class Quotas(core.ModelBase, QuotasBase):
|
||||
"""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'
|
||||
__table_args__ = ()
|
||||
attributes = ['id', 'project_id', 'resource',
|
||||
'hard_limit', 'allocated',
|
||||
'created_at', 'updated_at', 'deleted_at', 'deleted']
|
||||
|
||||
id = sql.Column(sql.Integer, primary_key=True)
|
||||
project_id = sql.Column(sql.String(255), index=True)
|
||||
resource = sql.Column(sql.String(255), nullable=False)
|
||||
hard_limit = sql.Column(sql.Integer)
|
||||
allocated = sql.Column(sql.Integer, default=0)
|
||||
|
||||
|
||||
class QuotaClasses(core.ModelBase, QuotasBase):
|
||||
"""Quota_classes.
|
||||
|
||||
make default quota as a update-able quota.Mainly for the command
|
||||
quota-class-show and quota-class-update
|
||||
"""
|
||||
__tablename__ = 'quota_classes'
|
||||
__table_args__ = ()
|
||||
attributes = ['id', 'class_name', 'resource', 'hard_limit',
|
||||
'created_at', 'updated_at', 'deleted_at', 'deleted']
|
||||
|
||||
id = sql.Column(sql.Integer, primary_key=True)
|
||||
class_name = sql.Column(sql.String(255), index=True)
|
||||
resource = sql.Column(sql.String(255), nullable=False)
|
||||
hard_limit = sql.Column(sql.Integer)
|
||||
|
||||
|
||||
class QuotaUsages(core.ModelBase, QuotasBase):
|
||||
"""Quota_uages.
|
||||
|
||||
store quota usages for project resource
|
||||
"""
|
||||
__tablename__ = 'quota_usages'
|
||||
__table_args__ = ()
|
||||
attributes = ['id', 'project_id', 'user_id', 'resource',
|
||||
'in_use', 'reserved', 'until_refresh',
|
||||
'created_at', 'updated_at', 'deleted_at', 'deleted']
|
||||
|
||||
id = sql.Column(sql.Integer, primary_key=True)
|
||||
project_id = sql.Column(sql.String(255), index=True)
|
||||
user_id = sql.Column(sql.String(255), index=True)
|
||||
resource = sql.Column(sql.String(255), nullable=False)
|
||||
|
||||
in_use = sql.Column(sql.Integer)
|
||||
reserved = sql.Column(sql.Integer, default=0)
|
||||
|
||||
until_refresh = sql.Column(sql.Integer, default=0)
|
||||
|
||||
@property
|
||||
def total(self):
|
||||
return self.in_use + self.reserved
|
||||
|
||||
|
||||
class Reservation(core.ModelBase, QuotasBase):
|
||||
"""Reservation.
|
||||
|
||||
Represents a resource reservation for quotas
|
||||
"""
|
||||
__tablename__ = 'reservations'
|
||||
__table_args__ = ()
|
||||
attributes = ['id', 'uuid', 'usage_id', 'project_id', 'resource',
|
||||
'delta', 'expire',
|
||||
'created_at', 'updated_at', 'deleted_at', 'deleted']
|
||||
|
||||
id = sql.Column(sql.Integer, primary_key=True)
|
||||
uuid = sql.Column(sql.String(36), nullable=False)
|
||||
|
||||
usage_id = sql.Column(sql.Integer,
|
||||
sql.ForeignKey('quota_usages.id'),
|
||||
nullable=False)
|
||||
|
||||
project_id = sql.Column(sql.String(255), index=True)
|
||||
resource = sql.Column(sql.String(255))
|
||||
|
||||
delta = sql.Column(sql.Integer)
|
||||
expire = sql.Column(sql.DateTime, nullable=False)
|
||||
|
||||
usage = relationship(
|
||||
"QuotaUsages",
|
||||
foreign_keys=usage_id,
|
||||
primaryjoin='and_(Reservation.usage_id == QuotaUsages.id,'
|
||||
'QuotaUsages.deleted == 0)')
|
||||
|
||||
|
||||
class VolumeTypeBase(models.ModelBase, core.DictBase,
|
||||
models.TimestampMixin, models.SoftDeleteMixin):
|
||||
"""VolumeTypeBase.
|
||||
|
||||
provide base class for volume type series tables. For it inherits from
|
||||
models.ModelBase, this is different from other tables
|
||||
"""
|
||||
__table_args__ = {'mysql_engine': 'InnoDB'}
|
||||
|
||||
metadata = None
|
||||
|
||||
def delete(self, session):
|
||||
"""Delete this object."""
|
||||
self.deleted = True
|
||||
self.deleted_at = timeutils.utcnow()
|
||||
self.save(session=session)
|
||||
|
||||
|
||||
class VolumeTypes(core.ModelBase, VolumeTypeBase):
|
||||
"""Represent possible volume_types of volumes offered."""
|
||||
__tablename__ = 'volume_types'
|
||||
attributes = ['id', 'name', 'description', 'qos_specs_id', 'is_public',
|
||||
'created_at', 'updated_at', 'deleted_at', 'deleted']
|
||||
|
||||
id = sql.Column(sql.String(36), primary_key=True)
|
||||
name = sql.Column(sql.String(255), unique=True)
|
||||
description = sql.Column(sql.String(255))
|
||||
# A reference to qos_specs entity
|
||||
qos_specs_id = sql.Column(sql.String(36),
|
||||
sql.ForeignKey('quality_of_service_specs.id'))
|
||||
is_public = sql.Column(sql.Boolean, default=True)
|
||||
|
||||
|
||||
class VolumeTypeProjects(core.ModelBase, VolumeTypeBase):
|
||||
"""Represent projects associated volume_types."""
|
||||
__tablename__ = 'volume_type_projects'
|
||||
__table_args__ = (schema.UniqueConstraint(
|
||||
'volume_type_id', 'project_id', 'deleted',
|
||||
name="uniq_volume_type_projects0volume_type_id0project_id0deleted"),
|
||||
)
|
||||
attributes = ['id', 'volume_type_id', 'project_id', 'created_at',
|
||||
'updated_at', 'deleted_at', 'deleted']
|
||||
id = sql.Column(sql.Integer, primary_key=True)
|
||||
volume_type_id = sql.Column(sql.String(36),
|
||||
sql.ForeignKey('volume_types.id'),
|
||||
nullable=False)
|
||||
project_id = sql.Column(sql.String(255))
|
||||
deleted = sql.Column(sql.Boolean, default=False)
|
||||
|
||||
volume_type = relationship(
|
||||
VolumeTypes,
|
||||
backref="projects",
|
||||
foreign_keys=volume_type_id,
|
||||
primaryjoin='and_('
|
||||
'VolumeTypeProjects.volume_type_id == VolumeTypes.id,'
|
||||
'VolumeTypeProjects.deleted == False)')
|
||||
|
||||
|
||||
class VolumeTypeExtraSpecs(core.ModelBase, VolumeTypeBase):
|
||||
"""Represents additional specs as key/value pairs for a volume_type."""
|
||||
__tablename__ = 'volume_type_extra_specs'
|
||||
attributes = ['id', 'key', 'value', 'volume_type_id', 'created_at',
|
||||
'updated_at', 'deleted', 'deleted_at']
|
||||
|
||||
id = sql.Column(sql.Integer, primary_key=True)
|
||||
key = sql.Column(sql.String(255))
|
||||
value = sql.Column(sql.String(255))
|
||||
volume_type_id = sql.Column(sql.String(36),
|
||||
sql.ForeignKey('volume_types.id'),
|
||||
nullable=False)
|
||||
volume_type = relationship(
|
||||
VolumeTypes,
|
||||
backref="extra_specs",
|
||||
foreign_keys=volume_type_id,
|
||||
primaryjoin='and_('
|
||||
'VolumeTypeExtraSpecs.volume_type_id == VolumeTypes.id,'
|
||||
'VolumeTypeExtraSpecs.deleted == False)'
|
||||
)
|
||||
|
||||
|
||||
class QualityOfServiceSpecs(core.ModelBase, core.DictBase,
|
||||
models.TimestampMixin):
|
||||
"""Represents QoS specs as key/value pairs.
|
||||
|
||||
QoS specs is standalone entity that can be associated/disassociated
|
||||
with volume types (one to many relation). Adjacency list relationship
|
||||
pattern is used in this model in order to represent following hierarchical
|
||||
data with in flat table, e.g, following structure
|
||||
|
||||
qos-specs-1 'Rate-Limit'
|
||||
|
|
||||
+------> consumer = 'front-end'
|
||||
+------> total_bytes_sec = 1048576
|
||||
+------> total_iops_sec = 500
|
||||
|
||||
qos-specs-2 'QoS_Level1'
|
||||
|
|
||||
+------> consumer = 'back-end'
|
||||
+------> max-iops = 1000
|
||||
+------> min-iops = 200
|
||||
|
||||
is represented by:
|
||||
|
||||
id specs_id key value
|
||||
------ -------- ------------- -----
|
||||
UUID-1 NULL QoSSpec_Name Rate-Limit
|
||||
UUID-2 UUID-1 consumer front-end
|
||||
UUID-3 UUID-1 total_bytes_sec 1048576
|
||||
UUID-4 UUID-1 total_iops_sec 500
|
||||
UUID-5 NULL QoSSpec_Name QoS_Level1
|
||||
UUID-6 UUID-5 consumer back-end
|
||||
UUID-7 UUID-5 max-iops 1000
|
||||
UUID-8 UUID-5 min-iops 200
|
||||
"""
|
||||
__tablename__ = 'quality_of_service_specs'
|
||||
attributes = ['id', 'specs_id', 'key', 'value', 'created_at', 'updated_at']
|
||||
|
||||
id = sql.Column(sql.String(36), primary_key=True)
|
||||
specs_id = sql.Column(sql.String(36), sql.ForeignKey(id))
|
||||
key = sql.Column(sql.String(255))
|
||||
value = sql.Column(sql.String(255))
|
||||
|
||||
|
||||
# Pod Model
|
||||
class Pod(core.ModelBase, core.DictBase):
|
||||
__tablename__ = 'cascaded_pods'
|
||||
|
|
Loading…
Reference in New Issue