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:
hejiawei 2016-12-14 19:09:40 +08:00
parent 81387881a7
commit 3a85ac7d4a
5 changed files with 2 additions and 1305 deletions

View File

@ -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

View File

@ -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.')

View File

@ -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

View File

@ -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],

View File

@ -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'