Add quotas per share type
With this feature it will be possible to set quotas per share type for all existing quota resources. It is useful for deployments with multiple backends that are accessible via different share types. Also, fix one of existing DB migrations that hangs on PostgreSQL. APIImpact DocImpact Implements blueprint support-quotas-per-share-type Change-Id: I8472418c2eb363cf5a76c672c7fdea72f21e4f63
This commit is contained in:
parent
b61f46452c
commit
05c42ecf70
|
@ -109,14 +109,14 @@ REST_API_VERSION_HISTORY = """
|
|||
* 2.37 - Added /messages APIs.
|
||||
* 2.38 - Support IPv6 validation in allow_access API to enable IPv6 in
|
||||
manila.
|
||||
|
||||
* 2.39 - Added share-type quotas.
|
||||
"""
|
||||
|
||||
# The minimum and maximum versions of the API supported
|
||||
# The default api version request is defined to be the
|
||||
# minimum version of the API supported.
|
||||
_MIN_API_VERSION = "2.0"
|
||||
_MAX_API_VERSION = "2.38"
|
||||
_MAX_API_VERSION = "2.39"
|
||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||
|
||||
|
||||
|
|
|
@ -218,3 +218,7 @@ user documentation.
|
|||
2.38
|
||||
----
|
||||
Support IPv6 format validation in allow_access API to enable IPv6.
|
||||
|
||||
2.39
|
||||
----
|
||||
Added share-type quotas.
|
||||
|
|
|
@ -19,6 +19,7 @@ from oslo_utils import strutils
|
|||
from six.moves.urllib import parse
|
||||
import webob
|
||||
|
||||
from manila.api.openstack import api_version_request as api_version
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api.views import quota_sets as quota_sets_views
|
||||
from manila import db
|
||||
|
@ -28,7 +29,7 @@ from manila import quota
|
|||
|
||||
QUOTAS = quota.QUOTAS
|
||||
LOG = log.getLogger(__name__)
|
||||
NON_QUOTA_KEYS = ('tenant_id', 'id', 'force')
|
||||
NON_QUOTA_KEYS = ('tenant_id', 'id', 'force', 'share_type')
|
||||
|
||||
|
||||
class QuotaSetsMixin(object):
|
||||
|
@ -41,7 +42,8 @@ class QuotaSetsMixin(object):
|
|||
resource_name = "quota_set"
|
||||
_view_builder_class = quota_sets_views.ViewBuilder
|
||||
|
||||
def _validate_quota_limit(self, limit, minimum, maximum, force_update):
|
||||
@staticmethod
|
||||
def _validate_quota_limit(limit, minimum, maximum, force_update):
|
||||
# NOTE: -1 is a flag value for unlimited
|
||||
if limit < -1:
|
||||
msg = _("Quota limit must be -1 or greater.")
|
||||
|
@ -50,17 +52,49 @@ class QuotaSetsMixin(object):
|
|||
(maximum != -1 or (maximum == -1 and limit != -1))):
|
||||
msg = _("Quota limit must be greater than %s.") % minimum
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
if maximum != -1 and limit > maximum:
|
||||
if maximum != -1 and limit > maximum and not force_update:
|
||||
msg = _("Quota limit must be less than %s.") % maximum
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
def _get_quotas(self, context, id, user_id=None, usages=False):
|
||||
if user_id:
|
||||
values = QUOTAS.get_user_quotas(context, id, user_id,
|
||||
usages=usages)
|
||||
else:
|
||||
values = QUOTAS.get_project_quotas(context, id, usages=usages)
|
||||
@staticmethod
|
||||
def _validate_user_id_and_share_type_args(user_id, share_type):
|
||||
if user_id and share_type:
|
||||
msg = _("'user_id' and 'share_type' values are mutually exclusive")
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
@staticmethod
|
||||
def _get_share_type_id(context, share_type_name_or_id):
|
||||
if share_type_name_or_id:
|
||||
share_type = db.share_type_get_by_name_or_id(
|
||||
context, share_type_name_or_id)
|
||||
if share_type:
|
||||
return share_type['id']
|
||||
msg = _("Share type with name or id '%s' not found.") % (
|
||||
share_type_name_or_id)
|
||||
raise webob.exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
@staticmethod
|
||||
def _ensure_share_type_arg_is_absent(req):
|
||||
params = parse.parse_qs(req.environ.get('QUERY_STRING', ''))
|
||||
share_type = params.get('share_type', [None])[0]
|
||||
if share_type:
|
||||
msg = _("'share_type' key is not supported by this microversion. "
|
||||
"Use 2.38 or greater microversion to be able "
|
||||
"to use 'share_type' quotas.")
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
def _get_quotas(self, context, project_id, user_id=None,
|
||||
share_type_id=None, usages=False):
|
||||
self._validate_user_id_and_share_type_args(user_id, share_type_id)
|
||||
if user_id:
|
||||
values = QUOTAS.get_user_quotas(
|
||||
context, project_id, user_id, usages=usages)
|
||||
elif share_type_id:
|
||||
values = QUOTAS.get_share_type_quotas(
|
||||
context, project_id, share_type_id, usages=usages)
|
||||
else:
|
||||
values = QUOTAS.get_project_quotas(
|
||||
context, project_id, usages=usages)
|
||||
if usages:
|
||||
return values
|
||||
return {k: v['limit'] for k, v in values.items()}
|
||||
|
@ -70,14 +104,15 @@ class QuotaSetsMixin(object):
|
|||
context = req.environ['manila.context']
|
||||
params = parse.parse_qs(req.environ.get('QUERY_STRING', ''))
|
||||
user_id = params.get('user_id', [None])[0]
|
||||
|
||||
share_type = params.get('share_type', [None])[0]
|
||||
try:
|
||||
db.authorize_project_context(context, id)
|
||||
# _get_quotas use 'usages' to indicate whether retrieve additional
|
||||
# attributes, so pass detail to the argument.
|
||||
return self._view_builder.detail_list(
|
||||
self._get_quotas(context, id, user_id=user_id,
|
||||
usages=detail), id)
|
||||
share_type_id = self._get_share_type_id(context, share_type)
|
||||
quotas = self._get_quotas(
|
||||
context, id, user_id, share_type_id, usages=detail)
|
||||
return self._view_builder.detail_list(quotas, id, share_type_id)
|
||||
except exception.NotAuthorized:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
|
||||
|
@ -94,19 +129,25 @@ class QuotaSetsMixin(object):
|
|||
force_update = False
|
||||
params = parse.parse_qs(req.environ.get('QUERY_STRING', ''))
|
||||
user_id = params.get('user_id', [None])[0]
|
||||
share_type = params.get('share_type', [None])[0]
|
||||
self._validate_user_id_and_share_type_args(user_id, share_type)
|
||||
share_type_id = self._get_share_type_id(context, share_type)
|
||||
|
||||
try:
|
||||
settable_quotas = QUOTAS.get_settable_quotas(context, project_id,
|
||||
user_id=user_id)
|
||||
settable_quotas = QUOTAS.get_settable_quotas(
|
||||
context, project_id, user_id=user_id,
|
||||
share_type_id=share_type_id)
|
||||
except exception.NotAuthorized:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
|
||||
for key, value in body.get('quota_set', {}).items():
|
||||
if (key not in QUOTAS and
|
||||
key not in NON_QUOTA_KEYS):
|
||||
if key == 'share_networks' and share_type_id:
|
||||
msg = _("'share_networks' quota cannot be set for share type. "
|
||||
"It can be set only for project or user.")
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
elif (key not in QUOTAS and key not in NON_QUOTA_KEYS):
|
||||
bad_keys.append(key)
|
||||
continue
|
||||
if key == 'force':
|
||||
elif key == 'force':
|
||||
force_update = strutils.bool_from_string(value)
|
||||
elif key not in NON_QUOTA_KEYS and value:
|
||||
try:
|
||||
|
@ -124,8 +165,9 @@ class QuotaSetsMixin(object):
|
|||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
try:
|
||||
quotas = self._get_quotas(context, id, user_id=user_id,
|
||||
usages=True)
|
||||
quotas = self._get_quotas(
|
||||
context, id, user_id=user_id, share_type_id=share_type_id,
|
||||
usages=True)
|
||||
except exception.NotAuthorized:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
|
||||
|
@ -164,25 +206,36 @@ class QuotaSetsMixin(object):
|
|||
maximum = settable_quotas[key]['maximum']
|
||||
self._validate_quota_limit(value, minimum, maximum, force_update)
|
||||
try:
|
||||
db.quota_create(context, project_id, key, value,
|
||||
user_id=user_id)
|
||||
db.quota_create(
|
||||
context, project_id, key, value,
|
||||
user_id=user_id, share_type_id=share_type_id)
|
||||
except exception.QuotaExists:
|
||||
db.quota_update(context, project_id, key, value,
|
||||
user_id=user_id)
|
||||
db.quota_update(
|
||||
context, project_id, key, value,
|
||||
user_id=user_id, share_type_id=share_type_id)
|
||||
except exception.AdminRequired:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
return self._view_builder.detail_list(
|
||||
self._get_quotas(context, id, user_id=user_id))
|
||||
self._get_quotas(
|
||||
context, id, user_id=user_id, share_type_id=share_type_id),
|
||||
share_type=share_type_id,
|
||||
)
|
||||
|
||||
@wsgi.Controller.authorize("delete")
|
||||
def _delete(self, req, id):
|
||||
context = req.environ['manila.context']
|
||||
params = parse.parse_qs(req.environ.get('QUERY_STRING', ''))
|
||||
user_id = params.get('user_id', [None])[0]
|
||||
share_type = params.get('share_type', [None])[0]
|
||||
self._validate_user_id_and_share_type_args(user_id, share_type)
|
||||
try:
|
||||
db.authorize_project_context(context, id)
|
||||
if user_id:
|
||||
QUOTAS.destroy_all_by_project_and_user(context, id, user_id)
|
||||
elif share_type:
|
||||
share_type_id = self._get_share_type_id(context, share_type)
|
||||
QUOTAS.destroy_all_by_project_and_share_type(
|
||||
context, id, share_type_id)
|
||||
else:
|
||||
QUOTAS.destroy_all_by_project(context, id)
|
||||
return webob.Response(status_int=202)
|
||||
|
@ -199,6 +252,7 @@ class QuotaSetsControllerLegacy(QuotaSetsMixin, wsgi.Controller):
|
|||
|
||||
@wsgi.Controller.api_version('1.0', '2.6')
|
||||
def show(self, req, id):
|
||||
self._ensure_share_type_arg_is_absent(req)
|
||||
return self._show(req, id)
|
||||
|
||||
@wsgi.Controller.api_version('1.0', '2.6')
|
||||
|
@ -207,10 +261,12 @@ class QuotaSetsControllerLegacy(QuotaSetsMixin, wsgi.Controller):
|
|||
|
||||
@wsgi.Controller.api_version('1.0', '2.6')
|
||||
def update(self, req, id, body):
|
||||
self._ensure_share_type_arg_is_absent(req)
|
||||
return self._update(req, id, body)
|
||||
|
||||
@wsgi.Controller.api_version('1.0', '2.6')
|
||||
def delete(self, req, id):
|
||||
self._ensure_share_type_arg_is_absent(req)
|
||||
return self._delete(req, id)
|
||||
|
||||
|
||||
|
@ -223,10 +279,14 @@ class QuotaSetsController(QuotaSetsMixin, wsgi.Controller):
|
|||
|
||||
@wsgi.Controller.api_version('2.7')
|
||||
def show(self, req, id):
|
||||
if req.api_version_request < api_version.APIVersionRequest("2.39"):
|
||||
self._ensure_share_type_arg_is_absent(req)
|
||||
return self._show(req, id)
|
||||
|
||||
@wsgi.Controller.api_version('2.25')
|
||||
def detail(self, req, id):
|
||||
if req.api_version_request < api_version.APIVersionRequest("2.39"):
|
||||
self._ensure_share_type_arg_is_absent(req)
|
||||
return self._show(req, id, True)
|
||||
|
||||
@wsgi.Controller.api_version('2.7')
|
||||
|
@ -235,10 +295,14 @@ class QuotaSetsController(QuotaSetsMixin, wsgi.Controller):
|
|||
|
||||
@wsgi.Controller.api_version('2.7')
|
||||
def update(self, req, id, body):
|
||||
if req.api_version_request < api_version.APIVersionRequest("2.39"):
|
||||
self._ensure_share_type_arg_is_absent(req)
|
||||
return self._update(req, id, body)
|
||||
|
||||
@wsgi.Controller.api_version('2.7')
|
||||
def delete(self, req, id):
|
||||
if req.api_version_request < api_version.APIVersionRequest("2.39"):
|
||||
self._ensure_share_type_arg_is_absent(req)
|
||||
return self._delete(req, id)
|
||||
|
||||
|
||||
|
|
|
@ -20,16 +20,17 @@ class ViewBuilder(common.ViewBuilder):
|
|||
|
||||
_collection_name = "quota_set"
|
||||
|
||||
def detail_list(self, quota_set, project_id=None):
|
||||
def detail_list(self, quota_set, project_id=None, share_type=None):
|
||||
"""Detailed view of quota set."""
|
||||
keys = (
|
||||
'shares',
|
||||
'gigabytes',
|
||||
'snapshots',
|
||||
'snapshot_gigabytes',
|
||||
'share_networks',
|
||||
)
|
||||
view = {key: quota_set.get(key) for key in keys}
|
||||
if project_id:
|
||||
view['id'] = project_id
|
||||
if not share_type:
|
||||
view['share_networks'] = quota_set.get('share_networks')
|
||||
return {self._collection_name: view}
|
||||
|
|
|
@ -136,15 +136,11 @@ def service_update(context, service_id, values):
|
|||
####################
|
||||
|
||||
|
||||
def quota_create(context, project_id, resource, limit, user_id=None):
|
||||
def quota_create(context, project_id, resource, limit, user_id=None,
|
||||
share_type_id=None):
|
||||
"""Create a quota for the given project and resource."""
|
||||
return IMPL.quota_create(context, project_id, resource, limit,
|
||||
user_id=user_id)
|
||||
|
||||
|
||||
def quota_get(context, project_id, resource, user_id=None):
|
||||
"""Retrieve a quota or raise if it does not exist."""
|
||||
return IMPL.quota_get(context, project_id, resource, user_id=user_id)
|
||||
user_id=user_id, share_type_id=share_type_id)
|
||||
|
||||
|
||||
def quota_get_all_by_project_and_user(context, project_id, user_id):
|
||||
|
@ -152,6 +148,13 @@ def quota_get_all_by_project_and_user(context, project_id, user_id):
|
|||
return IMPL.quota_get_all_by_project_and_user(context, project_id, user_id)
|
||||
|
||||
|
||||
def quota_get_all_by_project_and_share_type(context, project_id,
|
||||
share_type_id):
|
||||
"""Retrieve all quotas associated with a given project and user."""
|
||||
return IMPL.quota_get_all_by_project_and_share_type(
|
||||
context, project_id, share_type_id)
|
||||
|
||||
|
||||
def quota_get_all_by_project(context, project_id):
|
||||
"""Retrieve all quotas associated with a given project."""
|
||||
return IMPL.quota_get_all_by_project(context, project_id)
|
||||
|
@ -162,10 +165,11 @@ def quota_get_all(context, project_id):
|
|||
return IMPL.quota_get_all(context, project_id)
|
||||
|
||||
|
||||
def quota_update(context, project_id, resource, limit, user_id=None):
|
||||
def quota_update(context, project_id, resource, limit, user_id=None,
|
||||
share_type_id=None):
|
||||
"""Update a quota or raise if it does not exist."""
|
||||
return IMPL.quota_update(context, project_id, resource, limit,
|
||||
user_id=user_id)
|
||||
user_id=user_id, share_type_id=share_type_id)
|
||||
|
||||
|
||||
###################
|
||||
|
@ -199,9 +203,12 @@ def quota_class_update(context, class_name, resource, limit):
|
|||
###################
|
||||
|
||||
|
||||
def quota_usage_get(context, project_id, resource, user_id=None):
|
||||
def quota_usage_get(context, project_id, resource, user_id=None,
|
||||
share_type_id=None):
|
||||
"""Retrieve a quota usage or raise if it does not exist."""
|
||||
return IMPL.quota_usage_get(context, project_id, resource, user_id=user_id)
|
||||
return IMPL.quota_usage_get(
|
||||
context, project_id, resource, user_id=user_id,
|
||||
share_type_id=share_type_id)
|
||||
|
||||
|
||||
def quota_usage_get_all_by_project_and_user(context, project_id, user_id):
|
||||
|
@ -210,47 +217,61 @@ def quota_usage_get_all_by_project_and_user(context, project_id, user_id):
|
|||
project_id, user_id)
|
||||
|
||||
|
||||
def quota_usage_get_all_by_project_and_share_type(context, project_id,
|
||||
share_type_id):
|
||||
"""Retrieve all usage associated with a given resource."""
|
||||
return IMPL.quota_usage_get_all_by_project_and_share_type(
|
||||
context, project_id, share_type_id)
|
||||
|
||||
|
||||
def quota_usage_get_all_by_project(context, project_id):
|
||||
"""Retrieve all usage associated with a given resource."""
|
||||
return IMPL.quota_usage_get_all_by_project(context, project_id)
|
||||
|
||||
|
||||
def quota_usage_create(context, project_id, user_id, resource, in_use,
|
||||
reserved=0, until_refresh=None):
|
||||
reserved=0, until_refresh=None, share_type_id=None):
|
||||
"""Create a quota usage."""
|
||||
return IMPL.quota_usage_create(context, project_id, user_id, resource,
|
||||
in_use, reserved, until_refresh)
|
||||
return IMPL.quota_usage_create(
|
||||
context, project_id, user_id, resource, in_use, reserved,
|
||||
until_refresh, share_type_id=share_type_id)
|
||||
|
||||
|
||||
def quota_usage_update(context, project_id, user_id, resource, **kwargs):
|
||||
def quota_usage_update(context, project_id, user_id, resource,
|
||||
share_type_id=None, **kwargs):
|
||||
"""Update a quota usage or raise if it does not exist."""
|
||||
return IMPL.quota_usage_update(context, project_id, user_id, resource,
|
||||
**kwargs)
|
||||
return IMPL.quota_usage_update(
|
||||
context, project_id, user_id, resource, share_type_id=share_type_id,
|
||||
**kwargs)
|
||||
|
||||
|
||||
###################
|
||||
|
||||
|
||||
def quota_reserve(context, resources, quotas, user_quotas, deltas, expire,
|
||||
until_refresh, max_age, project_id=None, user_id=None):
|
||||
def quota_reserve(context, resources, quotas, user_quotas, share_type_quotas,
|
||||
deltas, expire, until_refresh, max_age,
|
||||
project_id=None, user_id=None, share_type_id=None):
|
||||
"""Check quotas and create appropriate reservations."""
|
||||
return IMPL.quota_reserve(context, resources, quotas, user_quotas, deltas,
|
||||
expire, until_refresh, max_age,
|
||||
project_id=project_id, user_id=user_id)
|
||||
return IMPL.quota_reserve(
|
||||
context, resources, quotas, user_quotas, share_type_quotas, deltas,
|
||||
expire, until_refresh, max_age, project_id=project_id, user_id=user_id,
|
||||
share_type_id=share_type_id)
|
||||
|
||||
|
||||
def reservation_commit(context, reservations, project_id=None, user_id=None):
|
||||
def reservation_commit(context, reservations, project_id=None, user_id=None,
|
||||
share_type_id=None):
|
||||
"""Commit quota reservations."""
|
||||
return IMPL.reservation_commit(context, reservations,
|
||||
project_id=project_id,
|
||||
user_id=user_id)
|
||||
return IMPL.reservation_commit(
|
||||
context, reservations, project_id=project_id, user_id=user_id,
|
||||
share_type_id=share_type_id)
|
||||
|
||||
|
||||
def reservation_rollback(context, reservations, project_id=None, user_id=None):
|
||||
def reservation_rollback(context, reservations, project_id=None, user_id=None,
|
||||
share_type_id=None):
|
||||
"""Roll back quota reservations."""
|
||||
return IMPL.reservation_rollback(context, reservations,
|
||||
project_id=project_id,
|
||||
user_id=user_id)
|
||||
return IMPL.reservation_rollback(
|
||||
context, reservations, project_id=project_id, user_id=user_id,
|
||||
share_type_id=share_type_id)
|
||||
|
||||
|
||||
def quota_destroy_all_by_project_and_user(context, project_id, user_id):
|
||||
|
@ -259,6 +280,13 @@ def quota_destroy_all_by_project_and_user(context, project_id, user_id):
|
|||
project_id, user_id)
|
||||
|
||||
|
||||
def quota_destroy_all_by_project_and_share_type(context, project_id,
|
||||
share_type_id):
|
||||
"""Destroy all quotas associated with a given project and user."""
|
||||
return IMPL.quota_destroy_all_by_project_and_share_type(
|
||||
context, project_id, share_type_id)
|
||||
|
||||
|
||||
def quota_destroy_all_by_project(context, project_id):
|
||||
"""Destroy all quotas associated with a given project."""
|
||||
return IMPL.quota_destroy_all_by_project(context, project_id)
|
||||
|
@ -770,6 +798,13 @@ def share_network_remove_security_service(context, id, security_service_id):
|
|||
security_service_id)
|
||||
|
||||
|
||||
def count_share_networks(context, project_id, user_id=None,
|
||||
share_type_id=None, session=None):
|
||||
return IMPL.count_share_networks(
|
||||
context, project_id, user_id=user_id, share_type_id=share_type_id,
|
||||
session=session,
|
||||
)
|
||||
|
||||
##################
|
||||
|
||||
|
||||
|
|
|
@ -23,21 +23,15 @@ revision = '7d142971c4ef'
|
|||
down_revision = 'd5db24264f5c'
|
||||
|
||||
from alembic import op
|
||||
from sqlalchemy import Index, MetaData, Table
|
||||
|
||||
|
||||
def _reservation_index(method):
|
||||
meta = MetaData()
|
||||
meta.bind = op.get_bind().engine
|
||||
reservations = Table('reservations', meta, autoload=True)
|
||||
index = Index('reservations_deleted_expire_idx',
|
||||
reservations.c.deleted, reservations.c.expire)
|
||||
getattr(index, method)(meta.bind)
|
||||
INDEX_NAME = 'reservations_deleted_expire_idx'
|
||||
TABLE_NAME = 'reservations'
|
||||
|
||||
|
||||
def upgrade():
|
||||
_reservation_index('create')
|
||||
op.create_index(INDEX_NAME, TABLE_NAME, ['deleted', 'expire'])
|
||||
|
||||
|
||||
def downgrade():
|
||||
_reservation_index('drop')
|
||||
op.drop_index(INDEX_NAME, TABLE_NAME)
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Add ProjectShareTypeQuota model
|
||||
|
||||
Revision ID: b516de97bfee
|
||||
Revises: 238720805ce1
|
||||
Create Date: 2017-03-27 15:11:11.449617
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'b516de97bfee'
|
||||
down_revision = '238720805ce1'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sql
|
||||
|
||||
NEW_TABLE_NAME = 'project_share_type_quotas'
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
NEW_TABLE_NAME,
|
||||
sql.Column('id', sql.Integer, primary_key=True, nullable=False),
|
||||
sql.Column('project_id', sql.String(length=255)),
|
||||
sql.Column('resource', sql.String(length=255), nullable=False),
|
||||
sql.Column('hard_limit', sql.Integer, nullable=True),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
sql.Column('deleted_at', sql.DateTime),
|
||||
sql.Column('deleted', sql.Integer, default=0),
|
||||
sql.Column(
|
||||
'share_type_id', sql.String(36),
|
||||
sql.ForeignKey(
|
||||
'share_types.id', name='share_type_id_fk',
|
||||
),
|
||||
nullable=False),
|
||||
sql.UniqueConstraint(
|
||||
'share_type_id', 'resource', 'deleted',
|
||||
name="uc_quotas_per_share_types"),
|
||||
mysql_engine='InnoDB',
|
||||
)
|
||||
for table_name in ('quota_usages', 'reservations'):
|
||||
op.add_column(
|
||||
table_name,
|
||||
sql.Column('share_type_id', sql.String(36), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table(NEW_TABLE_NAME)
|
||||
for table_name in ('quota_usages', 'reservations'):
|
||||
op.drop_column(table_name, 'share_type_id')
|
|
@ -35,6 +35,7 @@ from oslo_db import options as db_options
|
|||
from oslo_db.sqlalchemy import session
|
||||
from oslo_db.sqlalchemy import utils as db_utils
|
||||
from oslo_log import log
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
import six
|
||||
|
@ -279,40 +280,41 @@ def ensure_model_dict_has_id(model_dict):
|
|||
return model_dict
|
||||
|
||||
|
||||
def _sync_shares(context, project_id, user_id, session):
|
||||
(shares, gigs) = share_data_get_for_project(context,
|
||||
project_id,
|
||||
user_id,
|
||||
session=session)
|
||||
def _sync_shares(context, project_id, user_id, session, share_type_id=None):
|
||||
(shares, gigs) = share_data_get_for_project(
|
||||
context, project_id, user_id, share_type_id=share_type_id,
|
||||
session=session)
|
||||
return {'shares': shares}
|
||||
|
||||
|
||||
def _sync_snapshots(context, project_id, user_id, session):
|
||||
(snapshots, gigs) = snapshot_data_get_for_project(context,
|
||||
project_id,
|
||||
user_id,
|
||||
session=session)
|
||||
def _sync_snapshots(context, project_id, user_id, session, share_type_id=None):
|
||||
(snapshots, gigs) = snapshot_data_get_for_project(
|
||||
context, project_id, user_id, share_type_id=share_type_id,
|
||||
session=session)
|
||||
return {'snapshots': snapshots}
|
||||
|
||||
|
||||
def _sync_gigabytes(context, project_id, user_id, session):
|
||||
def _sync_gigabytes(context, project_id, user_id, session, share_type_id=None):
|
||||
_junk, share_gigs = share_data_get_for_project(
|
||||
context, project_id, user_id, session=session)
|
||||
return dict(gigabytes=share_gigs)
|
||||
context, project_id, user_id, share_type_id=share_type_id,
|
||||
session=session)
|
||||
return {"gigabytes": share_gigs}
|
||||
|
||||
|
||||
def _sync_snapshot_gigabytes(context, project_id, user_id, session):
|
||||
def _sync_snapshot_gigabytes(context, project_id, user_id, session,
|
||||
share_type_id=None):
|
||||
_junk, snapshot_gigs = snapshot_data_get_for_project(
|
||||
context, project_id, user_id, session=session)
|
||||
return dict(snapshot_gigabytes=snapshot_gigs)
|
||||
context, project_id, user_id, share_type_id=share_type_id,
|
||||
session=session)
|
||||
return {"snapshot_gigabytes": snapshot_gigs}
|
||||
|
||||
|
||||
def _sync_share_networks(context, project_id, user_id, session):
|
||||
share_networks = share_network_get_all_by_project(context,
|
||||
project_id,
|
||||
user_id,
|
||||
session=session)
|
||||
return {'share_networks': len(share_networks)}
|
||||
def _sync_share_networks(context, project_id, user_id, session,
|
||||
share_type_id=None):
|
||||
share_networks_count = count_share_networks(
|
||||
context, project_id, user_id, share_type_id=share_type_id,
|
||||
session=session)
|
||||
return {'share_networks': share_networks_count}
|
||||
|
||||
|
||||
QUOTA_SYNC_FUNCTIONS = {
|
||||
|
@ -459,50 +461,60 @@ def service_update(context, service_id, values):
|
|||
###################
|
||||
|
||||
|
||||
@require_context
|
||||
def quota_get(context, project_id, resource, session=None):
|
||||
result = (model_query(context, models.Quota, session=session,
|
||||
read_deleted="no").
|
||||
filter_by(project_id=project_id).
|
||||
filter_by(resource=resource).
|
||||
first())
|
||||
|
||||
if not result:
|
||||
raise exception.ProjectQuotaNotFound(project_id=project_id)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
def quota_get_all_by_project_and_user(context, project_id, user_id):
|
||||
authorize_project_context(context, project_id)
|
||||
|
||||
user_quotas = (model_query(context, models.ProjectUserQuota,
|
||||
models.ProjectUserQuota.resource,
|
||||
models.ProjectUserQuota.hard_limit).
|
||||
filter_by(project_id=project_id).
|
||||
filter_by(user_id=user_id).
|
||||
all())
|
||||
user_quotas = model_query(
|
||||
context, models.ProjectUserQuota,
|
||||
models.ProjectUserQuota.resource,
|
||||
models.ProjectUserQuota.hard_limit,
|
||||
).filter_by(
|
||||
project_id=project_id,
|
||||
).filter_by(
|
||||
user_id=user_id,
|
||||
).all()
|
||||
|
||||
result = {'project_id': project_id, 'user_id': user_id}
|
||||
for quota in user_quotas:
|
||||
result[quota.resource] = quota.hard_limit
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
def quota_get_all_by_project_and_share_type(context, project_id,
|
||||
share_type_id):
|
||||
authorize_project_context(context, project_id)
|
||||
share_type_quotas = model_query(
|
||||
context, models.ProjectShareTypeQuota,
|
||||
models.ProjectShareTypeQuota.resource,
|
||||
models.ProjectShareTypeQuota.hard_limit,
|
||||
).filter_by(
|
||||
project_id=project_id,
|
||||
).filter_by(
|
||||
share_type_id=share_type_id,
|
||||
).all()
|
||||
|
||||
result = {
|
||||
'project_id': project_id,
|
||||
'share_type_id': share_type_id,
|
||||
}
|
||||
for quota in share_type_quotas:
|
||||
result[quota.resource] = quota.hard_limit
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
def quota_get_all_by_project(context, project_id):
|
||||
authorize_project_context(context, project_id)
|
||||
|
||||
rows = (model_query(context, models.Quota, read_deleted="no").
|
||||
filter_by(project_id=project_id).
|
||||
all())
|
||||
project_quotas = model_query(
|
||||
context, models.Quota, read_deleted="no",
|
||||
).filter_by(
|
||||
project_id=project_id,
|
||||
).all()
|
||||
|
||||
result = {'project_id': project_id}
|
||||
for row in rows:
|
||||
result[row.resource] = row.hard_limit
|
||||
|
||||
for quota in project_quotas:
|
||||
result[quota.resource] = quota.hard_limit
|
||||
return result
|
||||
|
||||
|
||||
|
@ -518,26 +530,35 @@ def quota_get_all(context, project_id):
|
|||
|
||||
|
||||
@require_admin_context
|
||||
def quota_create(context, project_id, resource, limit, user_id=None):
|
||||
def quota_create(context, project_id, resource, limit, user_id=None,
|
||||
share_type_id=None):
|
||||
per_user = user_id and resource not in PER_PROJECT_QUOTAS
|
||||
|
||||
if per_user:
|
||||
check = (model_query(context, models.ProjectUserQuota).
|
||||
filter_by(project_id=project_id).
|
||||
filter_by(user_id=user_id).
|
||||
filter_by(resource=resource).
|
||||
all())
|
||||
check = model_query(context, models.ProjectUserQuota).filter(
|
||||
models.ProjectUserQuota.project_id == project_id,
|
||||
models.ProjectUserQuota.user_id == user_id,
|
||||
models.ProjectUserQuota.resource == resource,
|
||||
).all()
|
||||
quota_ref = models.ProjectUserQuota()
|
||||
quota_ref.user_id = user_id
|
||||
elif share_type_id:
|
||||
check = model_query(context, models.ProjectShareTypeQuota).filter(
|
||||
models.ProjectShareTypeQuota.project_id == project_id,
|
||||
models.ProjectShareTypeQuota.share_type_id == share_type_id,
|
||||
models.ProjectShareTypeQuota.resource == resource,
|
||||
).all()
|
||||
quota_ref = models.ProjectShareTypeQuota()
|
||||
quota_ref.share_type_id = share_type_id
|
||||
else:
|
||||
check = (model_query(context, models.Quota).
|
||||
filter_by(project_id=project_id).
|
||||
filter_by(resource=resource).
|
||||
all())
|
||||
check = model_query(context, models.Quota).filter(
|
||||
models.Quota.project_id == project_id,
|
||||
models.Quota.resource == resource,
|
||||
).all()
|
||||
quota_ref = models.Quota()
|
||||
if check:
|
||||
raise exception.QuotaExists(project_id=project_id, resource=resource)
|
||||
|
||||
quota_ref = models.ProjectUserQuota() if per_user else models.Quota()
|
||||
if per_user:
|
||||
quota_ref.user_id = user_id
|
||||
quota_ref.project_id = project_id
|
||||
quota_ref.resource = resource
|
||||
quota_ref.hard_limit = limit
|
||||
|
@ -549,22 +570,36 @@ def quota_create(context, project_id, resource, limit, user_id=None):
|
|||
|
||||
@require_admin_context
|
||||
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
|
||||
def quota_update(context, project_id, resource, limit, user_id=None):
|
||||
def quota_update(context, project_id, resource, limit, user_id=None,
|
||||
share_type_id=None):
|
||||
per_user = user_id and resource not in PER_PROJECT_QUOTAS
|
||||
model = models.ProjectUserQuota if per_user else models.Quota
|
||||
query = (model_query(context, model).
|
||||
filter_by(project_id=project_id).
|
||||
filter_by(resource=resource))
|
||||
if per_user:
|
||||
query = query.filter_by(user_id=user_id)
|
||||
query = model_query(context, models.ProjectUserQuota).filter(
|
||||
models.ProjectUserQuota.project_id == project_id,
|
||||
models.ProjectUserQuota.user_id == user_id,
|
||||
models.ProjectUserQuota.resource == resource,
|
||||
)
|
||||
elif share_type_id:
|
||||
query = model_query(context, models.ProjectShareTypeQuota).filter(
|
||||
models.ProjectShareTypeQuota.project_id == project_id,
|
||||
models.ProjectShareTypeQuota.share_type_id == share_type_id,
|
||||
models.ProjectShareTypeQuota.resource == resource,
|
||||
)
|
||||
else:
|
||||
query = model_query(context, models.Quota).filter(
|
||||
models.Quota.project_id == project_id,
|
||||
models.Quota.resource == resource,
|
||||
)
|
||||
|
||||
result = query.update({'hard_limit': limit})
|
||||
if not result:
|
||||
if per_user:
|
||||
raise exception.ProjectUserQuotaNotFound(project_id=project_id,
|
||||
user_id=user_id)
|
||||
else:
|
||||
raise exception.ProjectQuotaNotFound(project_id=project_id)
|
||||
raise exception.ProjectUserQuotaNotFound(
|
||||
project_id=project_id, user_id=user_id)
|
||||
elif share_type_id:
|
||||
raise exception.ProjectShareTypeQuotaNotFound(
|
||||
project_id=project_id, share_type=share_type_id)
|
||||
raise exception.ProjectQuotaNotFound(project_id=project_id)
|
||||
|
||||
|
||||
###################
|
||||
|
@ -640,7 +675,8 @@ def quota_class_update(context, class_name, resource, limit):
|
|||
|
||||
|
||||
@require_context
|
||||
def quota_usage_get(context, project_id, resource, user_id=None):
|
||||
def quota_usage_get(context, project_id, resource, user_id=None,
|
||||
share_type_id=None):
|
||||
query = (model_query(context, models.QuotaUsage, read_deleted="no").
|
||||
filter_by(project_id=project_id).
|
||||
filter_by(resource=resource))
|
||||
|
@ -649,6 +685,8 @@ def quota_usage_get(context, project_id, resource, user_id=None):
|
|||
result = query.filter_by(user_id=user_id).first()
|
||||
else:
|
||||
result = query.filter_by(user_id=None).first()
|
||||
elif share_type_id:
|
||||
result = query.filter_by(queryshare_type_id=share_type_id).first()
|
||||
else:
|
||||
result = query.first()
|
||||
|
||||
|
@ -658,7 +696,8 @@ def quota_usage_get(context, project_id, resource, user_id=None):
|
|||
return result
|
||||
|
||||
|
||||
def _quota_usage_get_all(context, project_id, user_id=None):
|
||||
def _quota_usage_get_all(context, project_id, user_id=None,
|
||||
share_type_id=None):
|
||||
authorize_project_context(context, project_id)
|
||||
query = (model_query(context, models.QuotaUsage, read_deleted="no").
|
||||
filter_by(project_id=project_id))
|
||||
|
@ -667,6 +706,11 @@ def _quota_usage_get_all(context, project_id, user_id=None):
|
|||
query = query.filter(or_(models.QuotaUsage.user_id == user_id,
|
||||
models.QuotaUsage.user_id is None))
|
||||
result['user_id'] = user_id
|
||||
elif share_type_id:
|
||||
query = query.filter_by(share_type_id=share_type_id)
|
||||
result['share_type_id'] = share_type_id
|
||||
else:
|
||||
query = query.filter_by(share_type_id=None)
|
||||
|
||||
rows = query.all()
|
||||
for row in rows:
|
||||
|
@ -690,11 +734,22 @@ def quota_usage_get_all_by_project_and_user(context, project_id, user_id):
|
|||
return _quota_usage_get_all(context, project_id, user_id=user_id)
|
||||
|
||||
|
||||
@require_context
|
||||
def quota_usage_get_all_by_project_and_share_type(context, project_id,
|
||||
share_type_id):
|
||||
return _quota_usage_get_all(
|
||||
context, project_id, share_type_id=share_type_id)
|
||||
|
||||
|
||||
def _quota_usage_create(context, project_id, user_id, resource, in_use,
|
||||
reserved, until_refresh, session=None):
|
||||
reserved, until_refresh, share_type_id=None,
|
||||
session=None):
|
||||
quota_usage_ref = models.QuotaUsage()
|
||||
if share_type_id:
|
||||
quota_usage_ref.share_type_id = share_type_id
|
||||
else:
|
||||
quota_usage_ref.user_id = user_id
|
||||
quota_usage_ref.project_id = project_id
|
||||
quota_usage_ref.user_id = user_id
|
||||
quota_usage_ref.resource = resource
|
||||
quota_usage_ref.in_use = in_use
|
||||
quota_usage_ref.reserved = reserved
|
||||
|
@ -709,27 +764,31 @@ def _quota_usage_create(context, project_id, user_id, resource, in_use,
|
|||
|
||||
@require_admin_context
|
||||
def quota_usage_create(context, project_id, user_id, resource, in_use,
|
||||
reserved, until_refresh):
|
||||
reserved, until_refresh, share_type_id=None):
|
||||
session = get_session()
|
||||
return _quota_usage_create(context, project_id, user_id, resource, in_use,
|
||||
reserved, until_refresh, session)
|
||||
return _quota_usage_create(
|
||||
context, project_id, user_id, resource, in_use, reserved,
|
||||
until_refresh, share_type_id=share_type_id, session=session)
|
||||
|
||||
|
||||
@require_admin_context
|
||||
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
|
||||
def quota_usage_update(context, project_id, user_id, resource, **kwargs):
|
||||
def quota_usage_update(context, project_id, user_id, resource,
|
||||
share_type_id=None, **kwargs):
|
||||
updates = {}
|
||||
|
||||
for key in ['in_use', 'reserved', 'until_refresh']:
|
||||
for key in ('in_use', 'reserved', 'until_refresh'):
|
||||
if key in kwargs:
|
||||
updates[key] = kwargs[key]
|
||||
|
||||
result = (model_query(context, models.QuotaUsage, read_deleted="no").
|
||||
filter_by(project_id=project_id).
|
||||
filter_by(resource=resource).
|
||||
filter(or_(models.QuotaUsage.user_id == user_id,
|
||||
models.QuotaUsage.user_id is None)).
|
||||
update(updates))
|
||||
query = model_query(
|
||||
context, models.QuotaUsage, read_deleted="no",
|
||||
).filter_by(project_id=project_id).filter_by(resource=resource)
|
||||
if share_type_id:
|
||||
query = query.filter_by(share_type_id=share_type_id)
|
||||
else:
|
||||
query = query.filter(or_(models.QuotaUsage.user_id == user_id,
|
||||
models.QuotaUsage.user_id is None))
|
||||
result = query.update(updates)
|
||||
|
||||
if not result:
|
||||
raise exception.QuotaUsageNotFound(project_id=project_id)
|
||||
|
@ -739,12 +798,15 @@ def quota_usage_update(context, project_id, user_id, resource, **kwargs):
|
|||
|
||||
|
||||
def _reservation_create(context, uuid, usage, project_id, user_id, resource,
|
||||
delta, expire, session=None):
|
||||
delta, expire, share_type_id=None, session=None):
|
||||
reservation_ref = models.Reservation()
|
||||
reservation_ref.uuid = uuid
|
||||
reservation_ref.usage_id = usage['id']
|
||||
reservation_ref.project_id = project_id
|
||||
reservation_ref.user_id = user_id
|
||||
if share_type_id:
|
||||
reservation_ref.share_type_id = share_type_id
|
||||
else:
|
||||
reservation_ref.user_id = user_id
|
||||
reservation_ref.resource = resource
|
||||
reservation_ref.delta = delta
|
||||
reservation_ref.expire = expire
|
||||
|
@ -760,6 +822,16 @@ def _reservation_create(context, uuid, usage, project_id, user_id, resource,
|
|||
# code always acquires the lock on quota_usages before acquiring the lock
|
||||
# on reservations.
|
||||
|
||||
def _get_share_type_quota_usages(context, session, project_id, share_type_id):
|
||||
rows = model_query(
|
||||
context, models.QuotaUsage, read_deleted="no", session=session,
|
||||
).filter(
|
||||
models.QuotaUsage.project_id == project_id,
|
||||
models.QuotaUsage.share_type_id == share_type_id,
|
||||
).with_lockmode('update').all()
|
||||
return {row.resource: row for row in rows}
|
||||
|
||||
|
||||
def _get_user_quota_usages(context, session, project_id, user_id):
|
||||
# Broken out for testability
|
||||
rows = (model_query(context, models.QuotaUsage,
|
||||
|
@ -778,6 +850,7 @@ def _get_project_quota_usages(context, session, project_id):
|
|||
read_deleted="no",
|
||||
session=session).
|
||||
filter_by(project_id=project_id).
|
||||
filter(models.QuotaUsage.share_type_id is None).
|
||||
with_lockmode('update').
|
||||
all())
|
||||
result = dict()
|
||||
|
@ -795,24 +868,49 @@ def _get_project_quota_usages(context, session, project_id):
|
|||
|
||||
|
||||
@require_context
|
||||
def quota_reserve(context, resources, project_quotas, user_quotas,
|
||||
share_type_quotas, deltas, expire, until_refresh,
|
||||
max_age, project_id=None, user_id=None, share_type_id=None):
|
||||
user_reservations = _quota_reserve(
|
||||
context, resources, project_quotas, user_quotas,
|
||||
deltas, expire, until_refresh, max_age, project_id, user_id=user_id)
|
||||
if share_type_id:
|
||||
try:
|
||||
st_reservations = _quota_reserve(
|
||||
context, resources, project_quotas, share_type_quotas,
|
||||
deltas, expire, until_refresh, max_age, project_id,
|
||||
share_type_id=share_type_id)
|
||||
except exception.OverQuota:
|
||||
with excutils.save_and_reraise_exception():
|
||||
# rollback previous reservations
|
||||
reservation_rollback(
|
||||
context, user_reservations,
|
||||
project_id=project_id, user_id=user_id)
|
||||
return user_reservations + st_reservations
|
||||
return user_reservations
|
||||
|
||||
|
||||
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
|
||||
def quota_reserve(context, resources, project_quotas, user_quotas, deltas,
|
||||
expire, until_refresh, max_age, project_id=None,
|
||||
user_id=None):
|
||||
def _quota_reserve(context, resources, project_quotas, user_or_st_quotas,
|
||||
deltas, expire, until_refresh,
|
||||
max_age, project_id=None, user_id=None, share_type_id=None):
|
||||
elevated = context.elevated()
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
|
||||
if project_id is None:
|
||||
project_id = context.project_id
|
||||
if user_id is None:
|
||||
user_id = context.user_id
|
||||
if share_type_id:
|
||||
user_or_st_usages = _get_share_type_quota_usages(
|
||||
context, session, project_id, share_type_id)
|
||||
else:
|
||||
user_id = user_id if user_id else context.user_id
|
||||
user_or_st_usages = _get_user_quota_usages(
|
||||
context, session, project_id, user_id)
|
||||
|
||||
# Get the current usages
|
||||
user_usages = _get_user_quota_usages(context, session,
|
||||
project_id, user_id)
|
||||
project_usages = _get_project_quota_usages(context, session,
|
||||
project_id)
|
||||
project_usages = _get_project_quota_usages(
|
||||
context, session, project_id)
|
||||
|
||||
# Handle usage refresh
|
||||
work = set(deltas.keys())
|
||||
|
@ -822,36 +920,38 @@ def quota_reserve(context, resources, project_quotas, user_quotas, deltas,
|
|||
# Do we need to refresh the usage?
|
||||
refresh = False
|
||||
if ((resource not in PER_PROJECT_QUOTAS) and
|
||||
(resource not in user_usages)):
|
||||
user_usages[resource] = _quota_usage_create(
|
||||
(resource not in user_or_st_usages)):
|
||||
user_or_st_usages[resource] = _quota_usage_create(
|
||||
elevated,
|
||||
project_id,
|
||||
user_id,
|
||||
resource,
|
||||
0, 0,
|
||||
until_refresh or None,
|
||||
share_type_id=share_type_id,
|
||||
session=session)
|
||||
refresh = True
|
||||
elif ((resource in PER_PROJECT_QUOTAS) and
|
||||
(resource not in user_usages)):
|
||||
user_usages[resource] = _quota_usage_create(
|
||||
(resource not in user_or_st_usages)):
|
||||
user_or_st_usages[resource] = _quota_usage_create(
|
||||
elevated,
|
||||
project_id,
|
||||
None,
|
||||
resource,
|
||||
0, 0,
|
||||
until_refresh or None,
|
||||
share_type_id=share_type_id,
|
||||
session=session)
|
||||
refresh = True
|
||||
elif user_usages[resource].in_use < 0:
|
||||
elif user_or_st_usages[resource].in_use < 0:
|
||||
# Negative in_use count indicates a desync, so try to
|
||||
# heal from that...
|
||||
refresh = True
|
||||
elif user_usages[resource].until_refresh is not None:
|
||||
user_usages[resource].until_refresh -= 1
|
||||
if user_usages[resource].until_refresh <= 0:
|
||||
elif user_or_st_usages[resource].until_refresh is not None:
|
||||
user_or_st_usages[resource].until_refresh -= 1
|
||||
if user_or_st_usages[resource].until_refresh <= 0:
|
||||
refresh = True
|
||||
elif max_age and (user_usages[resource].updated_at -
|
||||
elif max_age and (user_or_st_usages[resource].updated_at -
|
||||
timeutils.utcnow()).seconds >= max_age:
|
||||
refresh = True
|
||||
|
||||
|
@ -860,46 +960,54 @@ def quota_reserve(context, resources, project_quotas, user_quotas, deltas,
|
|||
# Grab the sync routine
|
||||
sync = QUOTA_SYNC_FUNCTIONS[resources[resource].sync]
|
||||
|
||||
updates = sync(elevated, project_id, user_id, session)
|
||||
updates = sync(
|
||||
elevated, project_id, user_id,
|
||||
share_type_id=share_type_id, session=session)
|
||||
for res, in_use in updates.items():
|
||||
# Make sure we have a destination for the usage!
|
||||
if ((res not in PER_PROJECT_QUOTAS) and
|
||||
(res not in user_usages)):
|
||||
user_usages[res] = _quota_usage_create(
|
||||
(res not in user_or_st_usages)):
|
||||
user_or_st_usages[res] = _quota_usage_create(
|
||||
elevated,
|
||||
project_id,
|
||||
user_id,
|
||||
res,
|
||||
0, 0,
|
||||
until_refresh or None,
|
||||
share_type_id=share_type_id,
|
||||
session=session)
|
||||
if ((res in PER_PROJECT_QUOTAS) and
|
||||
(res not in user_usages)):
|
||||
user_usages[res] = _quota_usage_create(
|
||||
(res not in user_or_st_usages)):
|
||||
user_or_st_usages[res] = _quota_usage_create(
|
||||
elevated,
|
||||
project_id,
|
||||
None,
|
||||
res,
|
||||
0, 0,
|
||||
until_refresh or None,
|
||||
share_type_id=share_type_id,
|
||||
session=session)
|
||||
|
||||
if user_usages[res].in_use != in_use:
|
||||
LOG.debug('quota_usages out of sync, updating. '
|
||||
'project_id: %(project_id)s, '
|
||||
'user_id: %(user_id)s, '
|
||||
'resource: %(res)s, '
|
||||
'tracked usage: %(tracked_use)s, '
|
||||
'actual usage: %(in_use)s',
|
||||
{'project_id': project_id,
|
||||
'user_id': user_id,
|
||||
'res': res,
|
||||
'tracked_use': user_usages[res].in_use,
|
||||
'in_use': in_use})
|
||||
if user_or_st_usages[res].in_use != in_use:
|
||||
LOG.debug(
|
||||
'quota_usages out of sync, updating. '
|
||||
'project_id: %(project_id)s, '
|
||||
'user_id: %(user_id)s, '
|
||||
'share_type_id: %(share_type_id)s, '
|
||||
'resource: %(res)s, '
|
||||
'tracked usage: %(tracked_use)s, '
|
||||
'actual usage: %(in_use)s',
|
||||
{'project_id': project_id,
|
||||
'user_id': user_id,
|
||||
'share_type_id': share_type_id,
|
||||
'res': res,
|
||||
'tracked_use': user_or_st_usages[res].in_use,
|
||||
'in_use': in_use})
|
||||
|
||||
# Update the usage
|
||||
user_usages[res].in_use = in_use
|
||||
user_usages[res].until_refresh = until_refresh or None
|
||||
user_or_st_usages[res].in_use = in_use
|
||||
user_or_st_usages[res].until_refresh = (
|
||||
until_refresh or None)
|
||||
|
||||
# Because more than one resource may be refreshed
|
||||
# by the call to the sync routine, and we don't
|
||||
|
@ -916,22 +1024,22 @@ def quota_reserve(context, resources, project_quotas, user_quotas, deltas,
|
|||
# Check for deltas that would go negative
|
||||
unders = [res for res, delta in deltas.items()
|
||||
if delta < 0 and
|
||||
delta + user_usages[res].in_use < 0]
|
||||
delta + user_or_st_usages[res].in_use < 0]
|
||||
|
||||
# Now, let's check the quotas
|
||||
# NOTE(Vek): We're only concerned about positive increments.
|
||||
# If a project has gone over quota, we want them to
|
||||
# be able to reduce their usage without any
|
||||
# problems.
|
||||
for key, value in user_usages.items():
|
||||
for key, value in user_or_st_usages.items():
|
||||
if key not in project_usages:
|
||||
project_usages[key] = value
|
||||
overs = [res for res, delta in deltas.items()
|
||||
if user_quotas[res] >= 0 and delta >= 0 and
|
||||
if user_or_st_quotas[res] >= 0 and delta >= 0 and
|
||||
(project_quotas[res] < delta +
|
||||
project_usages[res]['total'] or
|
||||
user_quotas[res] < delta +
|
||||
user_usages[res].total)]
|
||||
user_or_st_quotas[res] < delta +
|
||||
user_or_st_usages[res].total)]
|
||||
|
||||
# NOTE(Vek): The quota check needs to be in the transaction,
|
||||
# but the transaction doesn't fail just because
|
||||
|
@ -946,10 +1054,11 @@ def quota_reserve(context, resources, project_quotas, user_quotas, deltas,
|
|||
for res, delta in deltas.items():
|
||||
reservation = _reservation_create(elevated,
|
||||
uuidutils.generate_uuid(),
|
||||
user_usages[res],
|
||||
user_or_st_usages[res],
|
||||
project_id,
|
||||
user_id,
|
||||
res, delta, expire,
|
||||
share_type_id=share_type_id,
|
||||
session=session)
|
||||
reservations.append(reservation.uuid)
|
||||
|
||||
|
@ -966,24 +1075,24 @@ def quota_reserve(context, resources, project_quotas, user_quotas, deltas,
|
|||
# To prevent this, we only update the
|
||||
# reserved value if the delta is positive.
|
||||
if delta > 0:
|
||||
user_usages[res].reserved += delta
|
||||
user_or_st_usages[res].reserved += delta
|
||||
|
||||
# Apply updates to the usages table
|
||||
for usage_ref in user_usages.values():
|
||||
for usage_ref in user_or_st_usages.values():
|
||||
session.add(usage_ref)
|
||||
|
||||
if unders:
|
||||
LOG.warning("Change will make usage less than 0 for the following "
|
||||
"resources: %s", unders)
|
||||
if overs:
|
||||
if project_quotas == user_quotas:
|
||||
if project_quotas == user_or_st_quotas:
|
||||
usages = project_usages
|
||||
else:
|
||||
usages = user_usages
|
||||
usages = user_or_st_usages
|
||||
usages = {k: dict(in_use=v['in_use'], reserved=v['reserved'])
|
||||
for k, v in usages.items()}
|
||||
raise exception.OverQuota(overs=sorted(overs), quotas=user_quotas,
|
||||
usages=usages)
|
||||
raise exception.OverQuota(
|
||||
overs=sorted(overs), quotas=user_or_st_quotas, usages=usages)
|
||||
|
||||
return reservations
|
||||
|
||||
|
@ -1001,13 +1110,25 @@ def _quota_reservations_query(session, context, reservations):
|
|||
|
||||
@require_context
|
||||
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
|
||||
def reservation_commit(context, reservations, project_id=None, user_id=None):
|
||||
def reservation_commit(context, reservations, project_id=None, user_id=None,
|
||||
share_type_id=None):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
usages = _get_user_quota_usages(context, session, project_id, user_id)
|
||||
reservation_query = _quota_reservations_query(session, context,
|
||||
reservations)
|
||||
if share_type_id:
|
||||
st_usages = _get_share_type_quota_usages(
|
||||
context, session, project_id, share_type_id)
|
||||
else:
|
||||
st_usages = {}
|
||||
user_usages = _get_user_quota_usages(
|
||||
context, session, project_id, user_id)
|
||||
|
||||
reservation_query = _quota_reservations_query(
|
||||
session, context, reservations)
|
||||
for reservation in reservation_query.all():
|
||||
if reservation['share_type_id']:
|
||||
usages = st_usages
|
||||
else:
|
||||
usages = user_usages
|
||||
usage = usages[reservation.resource]
|
||||
if reservation.delta >= 0:
|
||||
usage.reserved -= reservation.delta
|
||||
|
@ -1017,13 +1138,25 @@ def reservation_commit(context, reservations, project_id=None, user_id=None):
|
|||
|
||||
@require_context
|
||||
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
|
||||
def reservation_rollback(context, reservations, project_id=None, user_id=None):
|
||||
def reservation_rollback(context, reservations, project_id=None, user_id=None,
|
||||
share_type_id=None):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
usages = _get_user_quota_usages(context, session, project_id, user_id)
|
||||
reservation_query = _quota_reservations_query(session, context,
|
||||
reservations)
|
||||
if share_type_id:
|
||||
st_usages = _get_share_type_quota_usages(
|
||||
context, session, project_id, share_type_id)
|
||||
else:
|
||||
st_usages = {}
|
||||
user_usages = _get_user_quota_usages(
|
||||
context, session, project_id, user_id)
|
||||
|
||||
reservation_query = _quota_reservations_query(
|
||||
session, context, reservations)
|
||||
for reservation in reservation_query.all():
|
||||
if reservation['share_type_id']:
|
||||
usages = st_usages
|
||||
else:
|
||||
usages = user_usages
|
||||
usage = usages[reservation.resource]
|
||||
if reservation.delta >= 0:
|
||||
usage.reserved -= reservation.delta
|
||||
|
@ -1050,6 +1183,37 @@ def quota_destroy_all_by_project_and_user(context, project_id, user_id):
|
|||
filter_by(user_id=user_id).soft_delete(synchronize_session=False))
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def quota_destroy_all_by_project_and_share_type(context, project_id,
|
||||
share_type_id):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
model_query(
|
||||
context, models.ProjectShareTypeQuota, session=session,
|
||||
read_deleted="no",
|
||||
).filter_by(
|
||||
project_id=project_id,
|
||||
).filter_by(
|
||||
share_type_id=share_type_id,
|
||||
).soft_delete(synchronize_session=False)
|
||||
|
||||
model_query(
|
||||
context, models.QuotaUsage, session=session, read_deleted="no",
|
||||
).filter_by(
|
||||
project_id=project_id,
|
||||
).filter_by(
|
||||
share_type_id=share_type_id,
|
||||
).soft_delete(synchronize_session=False)
|
||||
|
||||
model_query(
|
||||
context, models.Reservation, session=session, read_deleted="no",
|
||||
).filter_by(
|
||||
project_id=project_id,
|
||||
).filter_by(
|
||||
share_type_id=share_type_id,
|
||||
).soft_delete(synchronize_session=False)
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def quota_destroy_all_by_project(context, project_id):
|
||||
session = get_session()
|
||||
|
@ -1524,18 +1688,19 @@ def share_create(context, share_values, create_share_instance=True):
|
|||
|
||||
|
||||
@require_admin_context
|
||||
def share_data_get_for_project(context, project_id, user_id, session=None):
|
||||
def share_data_get_for_project(context, project_id, user_id,
|
||||
share_type_id=None, session=None):
|
||||
query = (model_query(context, models.Share,
|
||||
func.count(models.Share.id),
|
||||
func.sum(models.Share.size),
|
||||
read_deleted="no",
|
||||
session=session).
|
||||
filter_by(project_id=project_id))
|
||||
if user_id:
|
||||
result = query.filter_by(user_id=user_id).first()
|
||||
else:
|
||||
result = query.first()
|
||||
|
||||
if share_type_id:
|
||||
query = query.join("instances").filter_by(share_type_id=share_type_id)
|
||||
elif user_id:
|
||||
query = query.filter_by(user_id=user_id)
|
||||
result = query.first()
|
||||
return (result[0] or 0, result[1] or 0)
|
||||
|
||||
|
||||
|
@ -2161,7 +2326,8 @@ def share_snapshot_create(context, create_values,
|
|||
|
||||
|
||||
@require_admin_context
|
||||
def snapshot_data_get_for_project(context, project_id, user_id, session=None):
|
||||
def snapshot_data_get_for_project(context, project_id, user_id,
|
||||
share_type_id=None, session=None):
|
||||
query = (model_query(context, models.ShareSnapshot,
|
||||
func.count(models.ShareSnapshot.id),
|
||||
func.sum(models.ShareSnapshot.size),
|
||||
|
@ -2169,10 +2335,14 @@ def snapshot_data_get_for_project(context, project_id, user_id, session=None):
|
|||
session=session).
|
||||
filter_by(project_id=project_id))
|
||||
|
||||
if user_id:
|
||||
result = query.filter_by(user_id=user_id).first()
|
||||
else:
|
||||
result = query.first()
|
||||
if share_type_id:
|
||||
query = query.join(
|
||||
models.ShareInstance,
|
||||
models.ShareInstance.share_id == models.ShareSnapshot.share_id,
|
||||
).filter_by(share_type_id=share_type_id)
|
||||
elif user_id:
|
||||
query = query.filter_by(user_id=user_id)
|
||||
result = query.first()
|
||||
|
||||
return (result[0] or 0, result[1] or 0)
|
||||
|
||||
|
@ -3046,13 +3216,8 @@ def share_network_get_all(context):
|
|||
|
||||
|
||||
@require_context
|
||||
def share_network_get_all_by_project(context, project_id, user_id=None,
|
||||
session=None):
|
||||
query = _network_get_query(context, session)
|
||||
query = query.filter_by(project_id=project_id)
|
||||
if user_id is not None:
|
||||
query = query.filter_by(user_id=user_id)
|
||||
return query.all()
|
||||
def share_network_get_all_by_project(context, project_id):
|
||||
return _network_get_query(context).filter_by(project_id=project_id).all()
|
||||
|
||||
|
||||
@require_context
|
||||
|
@ -3123,6 +3288,22 @@ def share_network_remove_security_service(context, id, security_service_id):
|
|||
return share_nw_ref
|
||||
|
||||
|
||||
@require_context
|
||||
def count_share_networks(context, project_id, user_id=None,
|
||||
share_type_id=None, session=None):
|
||||
query = model_query(
|
||||
context, models.ShareNetwork,
|
||||
func.count(models.ShareNetwork.id),
|
||||
read_deleted="no",
|
||||
session=session).filter_by(project_id=project_id)
|
||||
if share_type_id:
|
||||
query = query.join("share_instances").filter_by(
|
||||
share_type_id=share_type_id)
|
||||
elif user_id is not None:
|
||||
query = query.filter_by(user_id=user_id)
|
||||
return query.first()[0]
|
||||
|
||||
|
||||
###################
|
||||
|
||||
|
||||
|
|
|
@ -108,6 +108,7 @@ class Quota(BASE, ManilaBase):
|
|||
project_id = Column(String(255), index=True)
|
||||
|
||||
resource = Column(String(255))
|
||||
|
||||
hard_limit = Column(Integer, nullable=True)
|
||||
|
||||
|
||||
|
@ -121,6 +122,19 @@ class ProjectUserQuota(BASE, ManilaBase):
|
|||
user_id = Column(String(255), nullable=False)
|
||||
|
||||
resource = Column(String(255), nullable=False)
|
||||
|
||||
hard_limit = Column(Integer)
|
||||
|
||||
|
||||
class ProjectShareTypeQuota(BASE, ManilaBase):
|
||||
"""Represents a single quota override for a share type within a project."""
|
||||
|
||||
__tablename__ = 'project_share_type_quotas'
|
||||
id = Column(Integer, primary_key=True, nullable=False)
|
||||
project_id = Column(String(255), nullable=False)
|
||||
share_type_id = Column(
|
||||
String(36), ForeignKey('share_types.id'), nullable=False)
|
||||
resource = Column(String(255), nullable=False)
|
||||
hard_limit = Column(Integer)
|
||||
|
||||
|
||||
|
@ -149,6 +163,7 @@ class QuotaUsage(BASE, ManilaBase):
|
|||
|
||||
project_id = Column(String(255), index=True)
|
||||
user_id = Column(String(255))
|
||||
share_type_id = Column(String(36))
|
||||
resource = Column(String(255))
|
||||
|
||||
in_use = Column(Integer)
|
||||
|
@ -172,6 +187,7 @@ class Reservation(BASE, ManilaBase):
|
|||
|
||||
project_id = Column(String(255), index=True)
|
||||
user_id = Column(String(255))
|
||||
share_type_id = Column(String(36))
|
||||
resource = Column(String(255))
|
||||
|
||||
delta = Column(Integer)
|
||||
|
|
|
@ -330,6 +330,11 @@ class ProjectUserQuotaNotFound(QuotaNotFound):
|
|||
"could not be found.")
|
||||
|
||||
|
||||
class ProjectShareTypeQuotaNotFound(QuotaNotFound):
|
||||
message = _("Quota for share_type %(share_type)s in "
|
||||
"project %(project_id)s could not be found.")
|
||||
|
||||
|
||||
class ProjectQuotaNotFound(QuotaNotFound):
|
||||
message = _("Quota for project %(project_id)s could not be found.")
|
||||
|
||||
|
@ -395,19 +400,27 @@ class QuotaError(ManilaException):
|
|||
|
||||
|
||||
class ShareSizeExceedsAvailableQuota(QuotaError):
|
||||
message = _("Requested share exceeds allowed gigabytes quota.")
|
||||
message = _(
|
||||
"Requested share exceeds allowed project/user or share type "
|
||||
"gigabytes quota.")
|
||||
|
||||
|
||||
class SnapshotSizeExceedsAvailableQuota(QuotaError):
|
||||
message = _("Requested snapshot exceeds allowed gigabytes quota.")
|
||||
message = _(
|
||||
"Requested snapshot exceeds allowed project/user or share type "
|
||||
"gigabytes quota.")
|
||||
|
||||
|
||||
class ShareLimitExceeded(QuotaError):
|
||||
message = _("Maximum number of shares allowed (%(allowed)d) exceeded.")
|
||||
message = _(
|
||||
"Maximum number of shares allowed (%(allowed)d) either per "
|
||||
"project/user or share type quota is exceeded.")
|
||||
|
||||
|
||||
class SnapshotLimitExceeded(QuotaError):
|
||||
message = _("Maximum number of snapshots allowed (%(allowed)d) exceeded.")
|
||||
message = _(
|
||||
"Maximum number of snapshots allowed (%(allowed)d) either per "
|
||||
"project/user or share type quota is exceeded.")
|
||||
|
||||
|
||||
class ShareNetworksLimitExceeded(QuotaError):
|
||||
|
|
384
manila/quota.py
384
manila/quota.py
|
@ -69,15 +69,6 @@ class DbQuotaDriver(object):
|
|||
quota information. The default driver utilizes the local
|
||||
database.
|
||||
"""
|
||||
def get_by_project_and_user(self, context, project_id, user_id, resource):
|
||||
"""Get a specific quota by project and user."""
|
||||
|
||||
return db.quota_get(context, project_id, user_id, resource)
|
||||
|
||||
def get_by_project(self, context, project_id, resource):
|
||||
"""Get a specific quota by project."""
|
||||
|
||||
return db.quota_get(context, project_id, resource)
|
||||
|
||||
def get_by_class(self, context, quota_class, resource):
|
||||
"""Get a specific quota by quota class."""
|
||||
|
@ -96,7 +87,6 @@ class DbQuotaDriver(object):
|
|||
for resource in resources.values():
|
||||
quotas[resource.name] = default_quotas.get(resource.name,
|
||||
resource.default)
|
||||
|
||||
return quotas
|
||||
|
||||
def get_class_quotas(self, context, resources, quota_class,
|
||||
|
@ -209,8 +199,7 @@ class DbQuotaDriver(object):
|
|||
remains=remains)
|
||||
|
||||
def get_user_quotas(self, context, resources, project_id, user_id,
|
||||
quota_class=None, defaults=True,
|
||||
usages=True):
|
||||
quota_class=None, defaults=True, usages=True):
|
||||
"""Retrieve quotas for user and project.
|
||||
|
||||
Given a list of resources, retrieve the quotas for the given
|
||||
|
@ -232,8 +221,8 @@ class DbQuotaDriver(object):
|
|||
:param usages: If True, the current in_use and reserved counts
|
||||
will also be returned.
|
||||
"""
|
||||
user_quotas = db.quota_get_all_by_project_and_user(context,
|
||||
project_id, user_id)
|
||||
user_quotas = db.quota_get_all_by_project_and_user(
|
||||
context, project_id, user_id)
|
||||
# Use the project quota for default user quota.
|
||||
proj_quotas = db.quota_get_all_by_project(context, project_id)
|
||||
for key, value in proj_quotas.items():
|
||||
|
@ -247,8 +236,47 @@ class DbQuotaDriver(object):
|
|||
user_quotas, quota_class,
|
||||
defaults=defaults, usages=user_usages)
|
||||
|
||||
def get_share_type_quotas(self, context, resources, project_id,
|
||||
share_type_id, quota_class=None, defaults=True,
|
||||
usages=True):
|
||||
"""Retrieve quotas for share_type and project.
|
||||
|
||||
Given a list of resources, retrieve the quotas for the given
|
||||
share_type and project.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
:param resources: A dictionary of the registered resources.
|
||||
:param project_id: The UUID of the project to return quotas for.
|
||||
:param share_type: UUID/name of a share type to return quotas for.
|
||||
:param quota_class: If project_id != context.project_id, the
|
||||
quota class cannot be determined. This
|
||||
parameter allows it to be specified. It
|
||||
will be ignored if project_id ==
|
||||
context.project_id.
|
||||
:param defaults: If True, the quota class value (or the
|
||||
default value, if there is no value from the
|
||||
quota class) will be reported if there is no
|
||||
specific value for the resource.
|
||||
:param usages: If True, the current in_use and reserved counts
|
||||
will also be returned.
|
||||
"""
|
||||
st_quotas = db.quota_get_all_by_project_and_share_type(
|
||||
context, project_id, share_type_id)
|
||||
# Use the project quota for default share_type quota.
|
||||
project_quotas = db.quota_get_all_by_project(context, project_id)
|
||||
for key, value in project_quotas.items():
|
||||
if key not in st_quotas.keys():
|
||||
st_quotas[key] = value
|
||||
st_usages = None
|
||||
if usages:
|
||||
st_usages = db.quota_usage_get_all_by_project_and_share_type(
|
||||
context, project_id, share_type_id)
|
||||
return self._process_quotas(
|
||||
context, resources, project_id, st_quotas, quota_class,
|
||||
defaults=defaults, usages=st_usages)
|
||||
|
||||
def get_settable_quotas(self, context, resources, project_id,
|
||||
user_id=None):
|
||||
user_id=None, share_type_id=None):
|
||||
"""Retrieve range of settable quotas.
|
||||
|
||||
Given a list of resources, retrieve the range of settable quotas for
|
||||
|
@ -258,30 +286,34 @@ class DbQuotaDriver(object):
|
|||
:param resources: A dictionary of the registered resources.
|
||||
:param project_id: The ID of the project to return quotas for.
|
||||
:param user_id: The ID of the user to return quotas for.
|
||||
:param share_type_id: The UUID of the share_type to return quotas for.
|
||||
"""
|
||||
settable_quotas = {}
|
||||
project_quotas = self.get_project_quotas(context, resources,
|
||||
project_id, remains=True)
|
||||
if user_id:
|
||||
user_quotas = self.get_user_quotas(context, resources,
|
||||
project_id, user_id)
|
||||
setted_quotas = db.quota_get_all_by_project_and_user(
|
||||
context, project_id, user_id)
|
||||
for key, value in user_quotas.items():
|
||||
maximum = (project_quotas[key]['remains'] +
|
||||
setted_quotas.get(key, 0))
|
||||
settable_quotas[key] = dict(
|
||||
minimum=value['in_use'] + value['reserved'],
|
||||
maximum=maximum)
|
||||
project_quotas = self.get_project_quotas(
|
||||
context, resources, project_id, remains=True)
|
||||
if user_id or share_type_id:
|
||||
if user_id:
|
||||
subquotas = self.get_user_quotas(
|
||||
context, resources, project_id, user_id)
|
||||
else:
|
||||
subquotas = self.get_share_type_quotas(
|
||||
context, resources, project_id, share_type_id)
|
||||
for key, value in subquotas.items():
|
||||
settable_quotas[key] = {
|
||||
"minimum": value['in_use'] + value['reserved'],
|
||||
"maximum": project_quotas[key]["limit"],
|
||||
}
|
||||
else:
|
||||
for key, value in project_quotas.items():
|
||||
minimum = max(int(value['limit'] - value['remains']),
|
||||
int(value['in_use'] + value['reserved']))
|
||||
settable_quotas[key] = dict(minimum=minimum, maximum=-1)
|
||||
minimum = max(
|
||||
int(value['limit'] - value['remains']),
|
||||
int(value['in_use'] + value['reserved'])
|
||||
)
|
||||
settable_quotas[key] = {"minimum": minimum, "maximum": -1}
|
||||
return settable_quotas
|
||||
|
||||
def _get_quotas(self, context, resources, keys, has_sync, project_id=None,
|
||||
user_id=None):
|
||||
user_id=None, share_type_id=None):
|
||||
"""Retrieve quotas for a resource.
|
||||
|
||||
A helper method which retrieves the quotas for the specific
|
||||
|
@ -323,6 +355,11 @@ class DbQuotaDriver(object):
|
|||
quotas = self.get_user_quotas(context, sub_resources,
|
||||
project_id, user_id,
|
||||
context.quota_class, usages=False)
|
||||
elif share_type_id:
|
||||
# Grab and return the quotas (without usages)
|
||||
quotas = self.get_share_type_quotas(
|
||||
context, sub_resources, project_id, share_type_id,
|
||||
context.quota_class, usages=False)
|
||||
else:
|
||||
# Grab and return the quotas (without usages)
|
||||
quotas = self.get_project_quotas(context, sub_resources,
|
||||
|
@ -332,66 +369,8 @@ class DbQuotaDriver(object):
|
|||
|
||||
return {k: v['limit'] for k, v in quotas.items()}
|
||||
|
||||
def limit_check(self, context, resources, values, project_id=None,
|
||||
user_id=None):
|
||||
"""Check simple quota limits.
|
||||
|
||||
For limits--those quotas for which there is no usage
|
||||
synchronization function--this method checks that a set of
|
||||
proposed values are permitted by the limit restriction.
|
||||
|
||||
This method will raise a QuotaResourceUnknown exception if a
|
||||
given resource is unknown or if it is not a simple limit
|
||||
resource.
|
||||
|
||||
If any of the proposed values is over the defined quota, an
|
||||
OverQuota exception will be raised with the sorted list of the
|
||||
resources which are too high. Otherwise, the method returns
|
||||
nothing.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
:param resources: A dictionary of the registered resources.
|
||||
:param values: A dictionary of the values to check against the
|
||||
quota.
|
||||
:param project_id: Specify the project_id if current context
|
||||
is admin and admin wants to impact on
|
||||
common user's tenant.
|
||||
:param user_id: Specify the user_id if current context
|
||||
is admin and admin wants to impact on
|
||||
common user. (Special case: user operates on
|
||||
resource, owned/created by different user)
|
||||
"""
|
||||
|
||||
# Ensure no value is less than zero
|
||||
unders = [key for key, val in values.items() if val < 0]
|
||||
if unders:
|
||||
raise exception.InvalidQuotaValue(unders=sorted(unders))
|
||||
|
||||
# If project_id is None, then we use the project_id in context
|
||||
if project_id is None:
|
||||
project_id = context.project_id
|
||||
# If user id is None, then we use the user_id in context
|
||||
if user_id is None:
|
||||
user_id = context.user_id
|
||||
|
||||
# Get the applicable quotas
|
||||
quotas = self._get_quotas(context, resources, values.keys(),
|
||||
has_sync=False, project_id=project_id)
|
||||
user_quotas = self._get_quotas(context, resources, values.keys(),
|
||||
has_sync=False, project_id=project_id,
|
||||
user_id=user_id)
|
||||
|
||||
# Check the quotas and construct a list of the resources that
|
||||
# would be put over limit by the desired values
|
||||
overs = [key for key, val in values.items()
|
||||
if (quotas[key] >= 0 and quotas[key] < val) or
|
||||
(user_quotas[key] >= 0 and user_quotas[key] < val)]
|
||||
if overs:
|
||||
raise exception.OverQuota(overs=sorted(overs), quotas=quotas,
|
||||
usages={})
|
||||
|
||||
def reserve(self, context, resources, deltas, expire=None,
|
||||
project_id=None, user_id=None):
|
||||
project_id=None, user_id=None, share_type_id=None):
|
||||
"""Check quotas and reserve resources.
|
||||
|
||||
For counting quotas--those quotas for which there is a usage
|
||||
|
@ -451,23 +430,31 @@ class DbQuotaDriver(object):
|
|||
# NOTE(Vek): We're not worried about races at this point.
|
||||
# Yes, the admin may be in the process of reducing
|
||||
# quotas, but that's a pretty rare thing.
|
||||
quotas = self._get_quotas(context, resources, deltas.keys(),
|
||||
has_sync=True, project_id=project_id)
|
||||
user_quotas = self._get_quotas(context, resources, deltas.keys(),
|
||||
has_sync=True, project_id=project_id,
|
||||
user_id=user_id)
|
||||
quotas = self._get_quotas(
|
||||
context, resources, deltas, has_sync=True, project_id=project_id)
|
||||
user_quotas = self._get_quotas(
|
||||
context, resources, deltas, has_sync=True,
|
||||
project_id=project_id, user_id=user_id)
|
||||
if share_type_id:
|
||||
share_type_quotas = self._get_quotas(
|
||||
context, resources, deltas, has_sync=True,
|
||||
project_id=project_id, share_type_id=share_type_id)
|
||||
else:
|
||||
share_type_quotas = {}
|
||||
|
||||
# NOTE(Vek): Most of the work here has to be done in the DB
|
||||
# API, because we have to do it in a transaction,
|
||||
# which means access to the session. Since the
|
||||
# session isn't available outside the DBAPI, we
|
||||
# have to do the work there.
|
||||
return db.quota_reserve(context, resources, quotas, user_quotas,
|
||||
deltas, expire,
|
||||
CONF.until_refresh, CONF.max_age,
|
||||
project_id=project_id, user_id=user_id)
|
||||
return db.quota_reserve(
|
||||
context, resources, quotas, user_quotas, share_type_quotas,
|
||||
deltas, expire, CONF.until_refresh, CONF.max_age,
|
||||
project_id=project_id, user_id=user_id,
|
||||
share_type_id=share_type_id)
|
||||
|
||||
def commit(self, context, reservations, project_id=None, user_id=None):
|
||||
def commit(self, context, reservations, project_id=None, user_id=None,
|
||||
share_type_id=None):
|
||||
"""Commit reservations.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
|
@ -488,10 +475,12 @@ class DbQuotaDriver(object):
|
|||
if user_id is None:
|
||||
user_id = context.user_id
|
||||
|
||||
db.reservation_commit(context, reservations, project_id=project_id,
|
||||
user_id=user_id)
|
||||
db.reservation_commit(
|
||||
context, reservations, project_id=project_id, user_id=user_id,
|
||||
share_type_id=share_type_id)
|
||||
|
||||
def rollback(self, context, reservations, project_id=None, user_id=None):
|
||||
def rollback(self, context, reservations, project_id=None, user_id=None,
|
||||
share_type_id=None):
|
||||
"""Roll back reservations.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
|
@ -512,8 +501,9 @@ class DbQuotaDriver(object):
|
|||
if user_id is None:
|
||||
user_id = context.user_id
|
||||
|
||||
db.reservation_rollback(context, reservations, project_id=project_id,
|
||||
user_id=user_id)
|
||||
db.reservation_rollback(
|
||||
context, reservations, project_id=project_id, user_id=user_id,
|
||||
share_type_id=share_type_id)
|
||||
|
||||
def usage_reset(self, context, resources):
|
||||
"""Reset usage records.
|
||||
|
@ -571,6 +561,21 @@ class DbQuotaDriver(object):
|
|||
|
||||
db.quota_destroy_all_by_project_and_user(context, project_id, user_id)
|
||||
|
||||
def destroy_all_by_project_and_share_type(self, context, project_id,
|
||||
share_type_id):
|
||||
"""Destroy metadata associated with a project and share_type.
|
||||
|
||||
Destroy all quotas, usages, and reservations associated with a
|
||||
project and share_type.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
:param project_id: The ID of the project.
|
||||
:param share_type_id: The UUID of the share type.
|
||||
"""
|
||||
|
||||
db.quota_destroy_all_by_project_and_share_type(
|
||||
context, project_id, share_type_id)
|
||||
|
||||
def expire(self, context):
|
||||
"""Expire reservations.
|
||||
|
||||
|
@ -598,54 +603,6 @@ class BaseResource(object):
|
|||
self.name = name
|
||||
self.flag = flag
|
||||
|
||||
def quota(self, driver, context, **kwargs):
|
||||
"""Obtain quota for a resource.
|
||||
|
||||
Given a driver and context, obtain the quota for this
|
||||
resource.
|
||||
|
||||
:param driver: A quota driver.
|
||||
:param context: The request context.
|
||||
:param project_id: The project to obtain the quota value for.
|
||||
If not provided, it is taken from the
|
||||
context. If it is given as None, no
|
||||
project-specific quota will be searched
|
||||
for.
|
||||
:param quota_class: The quota class corresponding to the
|
||||
project, or for which the quota is to be
|
||||
looked up. If not provided, it is taken
|
||||
from the context. If it is given as None,
|
||||
no quota class-specific quota will be
|
||||
searched for. Note that the quota class
|
||||
defaults to the value in the context,
|
||||
which may not correspond to the project if
|
||||
project_id is not the same as the one in
|
||||
the context.
|
||||
"""
|
||||
|
||||
# Get the project ID
|
||||
project_id = kwargs.get('project_id', context.project_id)
|
||||
|
||||
# Ditto for the quota class
|
||||
quota_class = kwargs.get('quota_class', context.quota_class)
|
||||
|
||||
# Look up the quota for the project
|
||||
if project_id:
|
||||
try:
|
||||
return driver.get_by_project(context, project_id, self.name)
|
||||
except exception.ProjectQuotaNotFound:
|
||||
pass
|
||||
|
||||
# Try for the quota class
|
||||
if quota_class:
|
||||
try:
|
||||
return driver.get_by_class(context, quota_class, self.name)
|
||||
except exception.QuotaClassNotFound:
|
||||
pass
|
||||
|
||||
# OK, return the default
|
||||
return self.default
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
"""Return the default value of the quota."""
|
||||
|
@ -768,17 +725,6 @@ class QuotaEngine(object):
|
|||
for resource in resources:
|
||||
self.register_resource(resource)
|
||||
|
||||
def get_by_project_and_user(self, context, project_id, user_id, resource):
|
||||
"""Get a specific quota by project and user."""
|
||||
|
||||
return self._driver.get_by_project_and_user(context, project_id,
|
||||
user_id, resource)
|
||||
|
||||
def get_by_project(self, context, project_id, resource):
|
||||
"""Get a specific quota by project."""
|
||||
|
||||
return self._driver.get_by_project(context, project_id, resource)
|
||||
|
||||
def get_by_class(self, context, quota_class, resource):
|
||||
"""Get a specific quota by quota class."""
|
||||
|
||||
|
@ -830,6 +776,28 @@ class QuotaEngine(object):
|
|||
defaults=defaults,
|
||||
usages=usages)
|
||||
|
||||
def get_share_type_quotas(self, context, project_id, share_type_id,
|
||||
quota_class=None, defaults=True, usages=True):
|
||||
"""Retrieve the quotas for the given user and project.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
:param project_id: The ID of the project to return quotas for.
|
||||
:param share_type_id: The UUID of the user to return quotas for.
|
||||
:param quota_class: If project_id != context.project_id, the
|
||||
quota class cannot be determined. This
|
||||
parameter allows it to be specified.
|
||||
:param defaults: If True, the quota class value (or the
|
||||
default value, if there is no value from the
|
||||
quota class) will be reported if there is no
|
||||
specific value for the resource.
|
||||
:param usages: If True, the current in_use and reserved counts
|
||||
will also be returned.
|
||||
"""
|
||||
|
||||
return self._driver.get_share_type_quotas(
|
||||
context, self._resources, project_id, share_type_id,
|
||||
quota_class=quota_class, defaults=defaults, usages=usages)
|
||||
|
||||
def get_project_quotas(self, context, project_id, quota_class=None,
|
||||
defaults=True, usages=True, remains=False):
|
||||
"""Retrieve the quotas for the given project.
|
||||
|
@ -856,7 +824,8 @@ class QuotaEngine(object):
|
|||
usages=usages,
|
||||
remains=remains)
|
||||
|
||||
def get_settable_quotas(self, context, project_id, user_id=None):
|
||||
def get_settable_quotas(self, context, project_id, user_id=None,
|
||||
share_type_id=None):
|
||||
"""Get settable quotas.
|
||||
|
||||
Given a list of resources, retrieve the range of settable quotas for
|
||||
|
@ -866,11 +835,12 @@ class QuotaEngine(object):
|
|||
:param resources: A dictionary of the registered resources.
|
||||
:param project_id: The ID of the project to return quotas for.
|
||||
:param user_id: The ID of the user to return quotas for.
|
||||
:param share_type_id: The UUID of the share_type to return quotas for.
|
||||
"""
|
||||
|
||||
return self._driver.get_settable_quotas(context, self._resources,
|
||||
project_id,
|
||||
user_id=user_id)
|
||||
return self._driver.get_settable_quotas(
|
||||
context, self._resources, project_id, user_id=user_id,
|
||||
share_type_id=share_type_id)
|
||||
|
||||
def count(self, context, resource, *args, **kwargs):
|
||||
"""Count a resource.
|
||||
|
@ -891,40 +861,8 @@ class QuotaEngine(object):
|
|||
|
||||
return res.count(context, *args, **kwargs)
|
||||
|
||||
def limit_check(self, context, project_id=None, user_id=None, **values):
|
||||
"""Check simple quota limits.
|
||||
|
||||
For limits--those quotas for which there is no usage
|
||||
synchronization function--this method checks that a set of
|
||||
proposed values are permitted by the limit restriction. The
|
||||
values to check are given as keyword arguments, where the key
|
||||
identifies the specific quota limit to check, and the value is
|
||||
the proposed value.
|
||||
|
||||
This method will raise a QuotaResourceUnknown exception if a
|
||||
given resource is unknown or if it is not a simple limit
|
||||
resource.
|
||||
|
||||
If any of the proposed values is over the defined quota, an
|
||||
OverQuota exception will be raised with the sorted list of the
|
||||
resources which are too high. Otherwise, the method returns
|
||||
nothing.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
:param project_id: Specify the project_id if current context
|
||||
is admin and admin wants to impact on
|
||||
common user's tenant.
|
||||
:param user_id: Specify the user_id if current context
|
||||
is admin and admin wants to impact on
|
||||
common user. (Special case: user operates on
|
||||
resource, owned/created by different user)
|
||||
"""
|
||||
|
||||
return self._driver.limit_check(context, self._resources, values,
|
||||
project_id=project_id, user_id=user_id)
|
||||
|
||||
def reserve(self, context, expire=None, project_id=None, user_id=None,
|
||||
**deltas):
|
||||
share_type_id=None, **deltas):
|
||||
"""Check quotas and reserve resources.
|
||||
|
||||
For counting quotas--those quotas for which there is a usage
|
||||
|
@ -959,16 +897,20 @@ class QuotaEngine(object):
|
|||
common user's tenant.
|
||||
"""
|
||||
|
||||
reservations = self._driver.reserve(context, self._resources, deltas,
|
||||
expire=expire,
|
||||
project_id=project_id,
|
||||
user_id=user_id)
|
||||
reservations = self._driver.reserve(
|
||||
context, self._resources, deltas,
|
||||
expire=expire,
|
||||
project_id=project_id,
|
||||
user_id=user_id,
|
||||
share_type_id=share_type_id,
|
||||
)
|
||||
|
||||
LOG.debug("Created reservations %s", reservations)
|
||||
|
||||
return reservations
|
||||
|
||||
def commit(self, context, reservations, project_id=None, user_id=None):
|
||||
def commit(self, context, reservations, project_id=None, user_id=None,
|
||||
share_type_id=None):
|
||||
"""Commit reservations.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
|
@ -980,8 +922,9 @@ class QuotaEngine(object):
|
|||
"""
|
||||
|
||||
try:
|
||||
self._driver.commit(context, reservations, project_id=project_id,
|
||||
user_id=user_id)
|
||||
self._driver.commit(
|
||||
context, reservations, project_id=project_id,
|
||||
user_id=user_id, share_type_id=share_type_id)
|
||||
except Exception:
|
||||
# NOTE(Vek): Ignoring exceptions here is safe, because the
|
||||
# usage resynchronization and the reservation expiration
|
||||
|
@ -992,7 +935,8 @@ class QuotaEngine(object):
|
|||
return
|
||||
LOG.debug("Committed reservations %s", reservations)
|
||||
|
||||
def rollback(self, context, reservations, project_id=None, user_id=None):
|
||||
def rollback(self, context, reservations, project_id=None, user_id=None,
|
||||
share_type_id=None):
|
||||
"""Roll back reservations.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
|
@ -1004,8 +948,9 @@ class QuotaEngine(object):
|
|||
"""
|
||||
|
||||
try:
|
||||
self._driver.rollback(context, reservations, project_id=project_id,
|
||||
user_id=user_id)
|
||||
self._driver.rollback(
|
||||
context, reservations, project_id=project_id,
|
||||
user_id=user_id, share_type_id=share_type_id)
|
||||
except Exception:
|
||||
# NOTE(Vek): Ignoring exceptions here is safe, because the
|
||||
# usage resynchronization and the reservation expiration
|
||||
|
@ -1048,6 +993,21 @@ class QuotaEngine(object):
|
|||
self._driver.destroy_all_by_project_and_user(context,
|
||||
project_id, user_id)
|
||||
|
||||
def destroy_all_by_project_and_share_type(self, context, project_id,
|
||||
share_type_id):
|
||||
"""Destroy metadata associated with a project and share_type.
|
||||
|
||||
Destroy all quotas, usages, and reservations associated with a
|
||||
project and share_type.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
:param project_id: The ID of the project.
|
||||
:param share_type_id: The UUID of the share_type.
|
||||
"""
|
||||
|
||||
self._driver.destroy_all_by_project_and_share_type(
|
||||
context, project_id, share_type_id)
|
||||
|
||||
def destroy_all_by_project(self, context, project_id):
|
||||
"""Destroy metadata associated with a project.
|
||||
|
||||
|
|
|
@ -138,7 +138,10 @@ class API(base.Base):
|
|||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
try:
|
||||
reservations = QUOTAS.reserve(context, shares=1, gigabytes=size)
|
||||
reservations = QUOTAS.reserve(
|
||||
context, shares=1, gigabytes=size,
|
||||
share_type_id=share_type_id,
|
||||
)
|
||||
except exception.OverQuota as e:
|
||||
overs = e.kwargs['overs']
|
||||
usages = e.kwargs['usages']
|
||||
|
@ -233,13 +236,14 @@ class API(base.Base):
|
|||
try:
|
||||
share = self.db.share_create(context, options,
|
||||
create_share_instance=False)
|
||||
QUOTAS.commit(context, reservations)
|
||||
QUOTAS.commit(context, reservations, share_type_id=share_type_id)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
try:
|
||||
self.db.share_delete(context, share['id'])
|
||||
finally:
|
||||
QUOTAS.rollback(context, reservations)
|
||||
QUOTAS.rollback(
|
||||
context, reservations, share_type_id=share_type_id)
|
||||
|
||||
host = None
|
||||
if snapshot and not CONF.use_scheduler_creating_share_from_snapshot:
|
||||
|
@ -778,7 +782,9 @@ class API(base.Base):
|
|||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
if reservations:
|
||||
QUOTAS.rollback(context, reservations)
|
||||
QUOTAS.rollback(
|
||||
context, reservations,
|
||||
share_type_id=share['instance']['share_type_id'])
|
||||
|
||||
def _handle_revert_to_snapshot_quotas(self, context, share, snapshot):
|
||||
"""Reserve extra quota if a revert will result in a larger share."""
|
||||
|
@ -789,10 +795,12 @@ class API(base.Base):
|
|||
return None
|
||||
|
||||
try:
|
||||
return QUOTAS.reserve(context,
|
||||
project_id=share['project_id'],
|
||||
gigabytes=size_increase,
|
||||
user_id=share['user_id'])
|
||||
return QUOTAS.reserve(
|
||||
context,
|
||||
project_id=share['project_id'],
|
||||
gigabytes=size_increase,
|
||||
user_id=share['user_id'],
|
||||
share_type_id=share['instance']['share_type_id'])
|
||||
except exception.OverQuota as exc:
|
||||
usages = exc.kwargs['usages']
|
||||
quotas = exc.kwargs['quotas']
|
||||
|
@ -919,11 +927,13 @@ class API(base.Base):
|
|||
try:
|
||||
# we give the user_id of the share, to update the quota usage
|
||||
# for the user, who created the share
|
||||
reservations = QUOTAS.reserve(context,
|
||||
project_id=project_id,
|
||||
shares=-1,
|
||||
gigabytes=-share['size'],
|
||||
user_id=share['user_id'])
|
||||
reservations = QUOTAS.reserve(
|
||||
context,
|
||||
project_id=project_id,
|
||||
shares=-1,
|
||||
gigabytes=-share['size'],
|
||||
user_id=share['user_id'],
|
||||
share_type_id=share['instance']['share_type_id'])
|
||||
except Exception as e:
|
||||
reservations = None
|
||||
LOG.exception(
|
||||
|
@ -938,8 +948,11 @@ class API(base.Base):
|
|||
if reservations:
|
||||
# we give the user_id of the share, to update the quota usage
|
||||
# for the user, who created the share
|
||||
QUOTAS.commit(context, reservations, project_id=project_id,
|
||||
user_id=share['user_id'])
|
||||
QUOTAS.commit(
|
||||
context, reservations, project_id=project_id,
|
||||
user_id=share['user_id'],
|
||||
share_type_id=share['instance']['share_type_id'],
|
||||
)
|
||||
|
||||
def delete_instance(self, context, share_instance, force=False):
|
||||
policy.check_policy(context, 'share', 'delete')
|
||||
|
@ -1010,7 +1023,8 @@ class API(base.Base):
|
|||
|
||||
try:
|
||||
reservations = QUOTAS.reserve(
|
||||
context, snapshots=1, snapshot_gigabytes=size)
|
||||
context, snapshots=1, snapshot_gigabytes=size,
|
||||
share_type_id=share['instance']['share_type_id'])
|
||||
except exception.OverQuota as e:
|
||||
overs = e.kwargs['overs']
|
||||
usages = e.kwargs['usages']
|
||||
|
@ -1049,13 +1063,17 @@ class API(base.Base):
|
|||
|
||||
try:
|
||||
snapshot = self.db.share_snapshot_create(context, options)
|
||||
QUOTAS.commit(context, reservations)
|
||||
QUOTAS.commit(
|
||||
context, reservations,
|
||||
share_type_id=share['instance']['share_type_id'])
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
try:
|
||||
self.db.snapshot_delete(context, share['id'])
|
||||
finally:
|
||||
QUOTAS.rollback(context, reservations)
|
||||
QUOTAS.rollback(
|
||||
context, reservations,
|
||||
share_type_id=share['instance']['share_type_id'])
|
||||
|
||||
# If replicated share, create snapshot instances for each replica
|
||||
if share.get('has_replicas'):
|
||||
|
@ -1793,10 +1811,12 @@ class API(base.Base):
|
|||
# we give the user_id of the share, to update the quota usage
|
||||
# for the user, who created the share, because on share delete
|
||||
# only this quota will be decreased
|
||||
reservations = QUOTAS.reserve(context,
|
||||
project_id=share['project_id'],
|
||||
gigabytes=size_increase,
|
||||
user_id=share['user_id'])
|
||||
reservations = QUOTAS.reserve(
|
||||
context,
|
||||
project_id=share['project_id'],
|
||||
gigabytes=size_increase,
|
||||
user_id=share['user_id'],
|
||||
share_type_id=share['instance']['share_type_id'])
|
||||
except exception.OverQuota as exc:
|
||||
usages = exc.kwargs['usages']
|
||||
quotas = exc.kwargs['quotas']
|
||||
|
|
|
@ -2199,12 +2199,18 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||
msg = _("Driver cannot calculate share size.")
|
||||
raise exception.InvalidShare(reason=msg)
|
||||
|
||||
reservations = QUOTAS.reserve(context,
|
||||
project_id=project_id,
|
||||
user_id=context.user_id,
|
||||
shares=1,
|
||||
gigabytes=share_update['size'])
|
||||
QUOTAS.commit(context, reservations, project_id=project_id)
|
||||
reservations = QUOTAS.reserve(
|
||||
context,
|
||||
project_id=project_id,
|
||||
user_id=context.user_id,
|
||||
shares=1,
|
||||
gigabytes=share_update['size'],
|
||||
share_type_id=share_instance['share_type_id'],
|
||||
)
|
||||
QUOTAS.commit(
|
||||
context, reservations, project_id=project_id,
|
||||
share_type_id=share_instance['share_type_id'],
|
||||
)
|
||||
|
||||
share_update.update({
|
||||
'status': constants.STATUS_AVAILABLE,
|
||||
|
@ -2371,11 +2377,17 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||
return
|
||||
|
||||
try:
|
||||
reservations = QUOTAS.reserve(context,
|
||||
project_id=project_id,
|
||||
shares=-1,
|
||||
gigabytes=-share_ref['size'])
|
||||
QUOTAS.commit(context, reservations, project_id=project_id)
|
||||
reservations = QUOTAS.reserve(
|
||||
context,
|
||||
project_id=project_id,
|
||||
shares=-1,
|
||||
gigabytes=-share_ref['size'],
|
||||
share_type_id=share_instance['share_type_id'],
|
||||
)
|
||||
QUOTAS.commit(
|
||||
context, reservations, project_id=project_id,
|
||||
share_type_id=share_instance['share_type_id'],
|
||||
)
|
||||
except Exception as e:
|
||||
# Note(imalinovskiy):
|
||||
# Quota reservation errors here are not fatal, because
|
||||
|
@ -2452,12 +2464,18 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||
return
|
||||
|
||||
try:
|
||||
share_type_id = snapshot_ref['share']['instance']['share_type_id']
|
||||
reservations = QUOTAS.reserve(
|
||||
context,
|
||||
project_id=project_id,
|
||||
snapshots=-1,
|
||||
snapshot_gigabytes=-snapshot_ref['size'])
|
||||
QUOTAS.commit(context, reservations, project_id=project_id)
|
||||
snapshot_gigabytes=-snapshot_ref['size'],
|
||||
share_type_id=share_type_id,
|
||||
)
|
||||
QUOTAS.commit(
|
||||
context, reservations, project_id=project_id,
|
||||
share_type_id=share_type_id,
|
||||
)
|
||||
except Exception as e:
|
||||
# Note(imalinovskiy):
|
||||
# Quota reservation errors here are not fatal, because
|
||||
|
@ -2500,6 +2518,7 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||
|
||||
snapshot_instance = self.db.share_snapshot_instance_get(
|
||||
context, snapshot.instance['id'], with_share_data=True)
|
||||
share_type_id = snapshot_instance["share_instance"]["share_type_id"]
|
||||
|
||||
# Make primitive to pass the information to the driver
|
||||
snapshot_instance_dict = self._get_snapshot_instance_dict(
|
||||
|
@ -2521,7 +2540,8 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||
if reservations:
|
||||
QUOTAS.rollback(
|
||||
context, reservations, project_id=project_id,
|
||||
user_id=user_id)
|
||||
user_id=user_id, share_type_id=share_type_id,
|
||||
)
|
||||
|
||||
self.db.share_update(
|
||||
context, share_id,
|
||||
|
@ -2532,7 +2552,9 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||
|
||||
if reservations:
|
||||
QUOTAS.commit(
|
||||
context, reservations, project_id=project_id, user_id=user_id)
|
||||
context, reservations, project_id=project_id, user_id=user_id,
|
||||
share_type_id=share_type_id,
|
||||
)
|
||||
|
||||
self.db.share_update(
|
||||
context, share_id,
|
||||
|
@ -2743,19 +2765,25 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||
|
||||
self.db.share_snapshot_instance_delete(context, snapshot_instance_id)
|
||||
|
||||
share_type_id = snapshot_ref['share']['instance']['share_type_id']
|
||||
try:
|
||||
reservations = QUOTAS.reserve(
|
||||
context, project_id=project_id, snapshots=-1,
|
||||
snapshot_gigabytes=-snapshot_ref['size'],
|
||||
user_id=snapshot_ref['user_id'])
|
||||
user_id=snapshot_ref['user_id'],
|
||||
share_type_id=share_type_id,
|
||||
)
|
||||
except Exception:
|
||||
reservations = None
|
||||
LOG.exception("Failed to update quota usages while deleting "
|
||||
"snapshot %s.", snapshot_id)
|
||||
|
||||
if reservations:
|
||||
QUOTAS.commit(context, reservations, project_id=project_id,
|
||||
user_id=snapshot_ref['user_id'])
|
||||
QUOTAS.commit(
|
||||
context, reservations, project_id=project_id,
|
||||
user_id=snapshot_ref['user_id'],
|
||||
share_type_id=share_type_id,
|
||||
)
|
||||
|
||||
@add_hooks
|
||||
@utils.require_driver_initialized
|
||||
|
@ -2860,7 +2888,9 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||
if reservations:
|
||||
QUOTAS.rollback(
|
||||
context, reservations, project_id=project_id,
|
||||
user_id=user_id)
|
||||
user_id=user_id,
|
||||
share_type_id=active_replica['share_type_id'],
|
||||
)
|
||||
|
||||
self.db.share_replica_update(
|
||||
context, active_replica['id'],
|
||||
|
@ -2871,7 +2901,9 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||
|
||||
if reservations:
|
||||
QUOTAS.commit(
|
||||
context, reservations, project_id=project_id, user_id=user_id)
|
||||
context, reservations, project_id=project_id, user_id=user_id,
|
||||
share_type_id=active_replica['share_type_id'],
|
||||
)
|
||||
|
||||
self.db.share_update(context, share_id, {'size': snapshot['size']})
|
||||
self.db.share_replica_update(
|
||||
|
@ -3368,14 +3400,19 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||
raise exception.ShareExtendingError(
|
||||
reason=six.text_type(e), share_id=share_id)
|
||||
finally:
|
||||
QUOTAS.rollback(context, reservations, project_id=project_id,
|
||||
user_id=user_id)
|
||||
QUOTAS.rollback(
|
||||
context, reservations, project_id=project_id,
|
||||
user_id=user_id,
|
||||
share_type_id=share_instance['share_type_id'],
|
||||
)
|
||||
|
||||
# we give the user_id of the share, to update the quota usage
|
||||
# for the user, who created the share, because on share delete
|
||||
# only this quota will be decreased
|
||||
QUOTAS.commit(context, reservations, project_id=project_id,
|
||||
user_id=user_id)
|
||||
QUOTAS.commit(
|
||||
context, reservations, project_id=project_id,
|
||||
user_id=user_id, share_type_id=share_instance['share_type_id'],
|
||||
)
|
||||
|
||||
share_update = {
|
||||
'size': int(new_size),
|
||||
|
@ -3418,10 +3455,13 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||
# we give the user_id of the share, to update the quota usage
|
||||
# for the user, who created the share, because on share delete
|
||||
# only this quota will be decreased
|
||||
reservations = QUOTAS.reserve(context,
|
||||
project_id=project_id,
|
||||
user_id=user_id,
|
||||
gigabytes=-size_decrease)
|
||||
reservations = QUOTAS.reserve(
|
||||
context,
|
||||
project_id=project_id,
|
||||
user_id=user_id,
|
||||
share_type_id=share_instance['share_type_id'],
|
||||
gigabytes=-size_decrease,
|
||||
)
|
||||
except Exception as e:
|
||||
error_occurred(
|
||||
e, ("Failed to update quota on share shrinking."))
|
||||
|
@ -3443,11 +3483,16 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||
try:
|
||||
error_occurred(e, **error_params)
|
||||
finally:
|
||||
QUOTAS.rollback(context, reservations, project_id=project_id,
|
||||
user_id=user_id)
|
||||
QUOTAS.rollback(
|
||||
context, reservations, project_id=project_id,
|
||||
user_id=user_id,
|
||||
share_type_id=share_instance['share_type_id'],
|
||||
)
|
||||
|
||||
QUOTAS.commit(context, reservations, project_id=project_id,
|
||||
user_id=user_id)
|
||||
QUOTAS.commit(
|
||||
context, reservations, project_id=project_id,
|
||||
user_id=user_id, share_type_id=share_instance['share_type_id'],
|
||||
)
|
||||
|
||||
share_update = {
|
||||
'size': new_size,
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
# under the License.
|
||||
|
||||
"""
|
||||
Tests for manila.api.v1.quota_sets.py
|
||||
Tests for manila.api.v2.quota_sets.py
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
@ -37,7 +37,7 @@ from manila import utils
|
|||
|
||||
CONF = cfg.CONF
|
||||
|
||||
REQ = mock.MagicMock()
|
||||
REQ = mock.MagicMock(api_version_request=api_version.APIVersionRequest("2.39"))
|
||||
REQ.environ = {'manila.context': context.get_admin_context()}
|
||||
REQ.environ['manila.context'].is_admin = True
|
||||
REQ.environ['manila.context'].auth_token = 'foo_auth_token'
|
||||
|
@ -122,6 +122,160 @@ class QuotaSetsControllerTest(test.TestCase):
|
|||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
@staticmethod
|
||||
def _get_share_type_request_object(microversion=None):
|
||||
req = copy.deepcopy(REQ)
|
||||
req.environ['QUERY_STRING'] = 'share_type=fake_share_type_name_or_id'
|
||||
req.api_version_request = api_version.APIVersionRequest(
|
||||
microversion or '2.39')
|
||||
return req
|
||||
|
||||
def test_share_type_quota_detail(self):
|
||||
self.mock_object(
|
||||
quota_sets.db, 'share_type_get_by_name_or_id',
|
||||
mock.Mock(return_value={'id': 'fake_st_id'}))
|
||||
req = self._get_share_type_request_object('2.39')
|
||||
quotas = {
|
||||
"shares": 23,
|
||||
"snapshots": 34,
|
||||
"gigabytes": 45,
|
||||
"snapshot_gigabytes": 56,
|
||||
}
|
||||
expected = {'quota_set': {
|
||||
'id': self.project_id,
|
||||
'shares': {
|
||||
'in_use': 0,
|
||||
'limit': quotas['shares'],
|
||||
'reserved': 0,
|
||||
},
|
||||
'gigabytes': {
|
||||
'in_use': 0,
|
||||
'limit': quotas['gigabytes'],
|
||||
'reserved': 0,
|
||||
},
|
||||
'snapshots': {
|
||||
'in_use': 0,
|
||||
'limit': quotas['snapshots'],
|
||||
'reserved': 0,
|
||||
},
|
||||
'snapshot_gigabytes': {
|
||||
'in_use': 0,
|
||||
'limit': quotas['snapshot_gigabytes'],
|
||||
'reserved': 0,
|
||||
},
|
||||
}}
|
||||
|
||||
for k, v in quotas.items():
|
||||
CONF.set_default('quota_' + k, v)
|
||||
|
||||
result = self.controller.detail(req, self.project_id)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
self.mock_policy_check.assert_called_once_with(
|
||||
req.environ['manila.context'], self.resource_name, 'show')
|
||||
quota_sets.db.share_type_get_by_name_or_id.assert_called_once_with(
|
||||
req.environ['manila.context'], 'fake_share_type_name_or_id')
|
||||
|
||||
def test_show_share_type_quota(self):
|
||||
self.mock_object(
|
||||
quota_sets.db, 'share_type_get_by_name_or_id',
|
||||
mock.Mock(return_value={'id': 'fake_st_id'}))
|
||||
req = self._get_share_type_request_object('2.39')
|
||||
quotas = {
|
||||
"shares": 23,
|
||||
"snapshots": 34,
|
||||
"gigabytes": 45,
|
||||
"snapshot_gigabytes": 56,
|
||||
}
|
||||
expected = {
|
||||
'quota_set': {
|
||||
'id': self.project_id,
|
||||
'shares': quotas.get('shares', 50),
|
||||
'gigabytes': quotas.get('gigabytes', 1000),
|
||||
'snapshots': quotas.get('snapshots', 50),
|
||||
'snapshot_gigabytes': quotas.get('snapshot_gigabytes', 1000),
|
||||
}
|
||||
}
|
||||
for k, v in quotas.items():
|
||||
CONF.set_default('quota_' + k, v)
|
||||
|
||||
result = self.controller.show(req, self.project_id)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
self.mock_policy_check.assert_called_once_with(
|
||||
req.environ['manila.context'], self.resource_name, 'show')
|
||||
quota_sets.db.share_type_get_by_name_or_id.assert_called_once_with(
|
||||
req.environ['manila.context'], 'fake_share_type_name_or_id')
|
||||
|
||||
@ddt.data('show', 'detail')
|
||||
def test_get_share_type_quota_with_old_microversion(self, method):
|
||||
req = self._get_share_type_request_object('2.38')
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPBadRequest,
|
||||
getattr(self.controller, method),
|
||||
req, self.project_id)
|
||||
|
||||
@ddt.data((None, None), (None, 'foo'), ('bar', None))
|
||||
@ddt.unpack
|
||||
def test__validate_user_id_and_share_type_args(self, user_id, st_id):
|
||||
result = self.controller._validate_user_id_and_share_type_args(
|
||||
user_id, st_id)
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test__validate_user_id_and_share_type_args_exception(self):
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPBadRequest,
|
||||
self.controller._validate_user_id_and_share_type_args,
|
||||
'foo', 'bar')
|
||||
|
||||
def test__get_share_type_id_found(self):
|
||||
self.mock_object(
|
||||
quota_sets.db, 'share_type_get_by_name_or_id',
|
||||
mock.Mock(return_value={'id': 'fake_st_id'}))
|
||||
ctxt = 'fake_context'
|
||||
share_type = 'fake_share_type_name_or_id'
|
||||
|
||||
result = self.controller._get_share_type_id(ctxt, share_type)
|
||||
|
||||
self.assertEqual('fake_st_id', result)
|
||||
|
||||
def test__get_share_type_id_not_found(self):
|
||||
self.mock_object(
|
||||
quota_sets.db, 'share_type_get_by_name_or_id',
|
||||
mock.Mock(return_value=None))
|
||||
ctxt = 'fake_context'
|
||||
share_type = 'fake_share_type_name_or_id'
|
||||
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPNotFound,
|
||||
self.controller._get_share_type_id,
|
||||
ctxt, share_type)
|
||||
|
||||
def test__get_share_type_id_is_not_provided(self):
|
||||
self.mock_object(
|
||||
quota_sets.db, 'share_type_get_by_name_or_id',
|
||||
mock.Mock(return_value={'id': 'fake_st_id'}))
|
||||
ctxt = 'fake_context'
|
||||
|
||||
result = self.controller._get_share_type_id(ctxt, None)
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
@ddt.data(REQ, REQ_WITH_USER)
|
||||
def test__ensure_share_type_arg_is_absent(self, req):
|
||||
result = self.controller._ensure_share_type_arg_is_absent(req)
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test__ensure_share_type_arg_is_absent_exception(self):
|
||||
req = self._get_share_type_request_object('2.39')
|
||||
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPBadRequest,
|
||||
self.controller._ensure_share_type_arg_is_absent,
|
||||
req)
|
||||
|
||||
@ddt.data(REQ, REQ_WITH_USER)
|
||||
def test_quota_detail(self, request):
|
||||
request.api_version_request = api_version.APIVersionRequest('2.25')
|
||||
|
@ -207,6 +361,10 @@ class QuotaSetsControllerTest(test.TestCase):
|
|||
|
||||
@ddt.data(REQ, REQ_WITH_USER)
|
||||
def test_update_quota(self, request):
|
||||
self.mock_object(
|
||||
quota_sets.db, 'share_type_get_by_name_or_id',
|
||||
mock.Mock(
|
||||
return_value={'id': 'fake_st_id', 'name': 'fake_st_name'}))
|
||||
CONF.set_default('quota_shares', 789)
|
||||
body = {'quota_set': {'tenant_id': self.project_id, 'shares': 788}}
|
||||
expected = {
|
||||
|
@ -234,6 +392,79 @@ class QuotaSetsControllerTest(test.TestCase):
|
|||
self.assertEqual(expected, show_result)
|
||||
self.mock_policy_check.assert_has_calls([
|
||||
mock_policy_update_check_call, mock_policy_show_check_call])
|
||||
quota_sets.db.share_type_get_by_name_or_id.assert_not_called()
|
||||
|
||||
def test_update_share_type_quota(self):
|
||||
self.mock_object(
|
||||
quota_sets.db, 'share_type_get_by_name_or_id',
|
||||
mock.Mock(
|
||||
return_value={'id': 'fake_st_id', 'name': 'fake_st_name'}))
|
||||
req = self._get_share_type_request_object('2.39')
|
||||
|
||||
CONF.set_default('quota_shares', 789)
|
||||
body = {'quota_set': {'tenant_id': self.project_id, 'shares': 788}}
|
||||
expected = {
|
||||
'quota_set': {
|
||||
'shares': body['quota_set']['shares'],
|
||||
'gigabytes': 1000,
|
||||
'snapshots': 50,
|
||||
'snapshot_gigabytes': 1000,
|
||||
}
|
||||
}
|
||||
|
||||
update_result = self.controller.update(req, self.project_id, body=body)
|
||||
|
||||
self.assertEqual(expected, update_result)
|
||||
quota_sets.db.share_type_get_by_name_or_id.assert_called_once_with(
|
||||
req.environ['manila.context'],
|
||||
req.environ['QUERY_STRING'].split('=')[-1])
|
||||
quota_sets.db.share_type_get_by_name_or_id.reset_mock()
|
||||
|
||||
show_result = self.controller.show(req, self.project_id)
|
||||
|
||||
expected['quota_set']['id'] = self.project_id
|
||||
self.assertEqual(expected, show_result)
|
||||
self.mock_policy_check.assert_has_calls([
|
||||
mock.call(req.environ['manila.context'], self.resource_name, key)
|
||||
for key in ('update', 'show')
|
||||
])
|
||||
quota_sets.db.share_type_get_by_name_or_id.assert_called_once_with(
|
||||
req.environ['manila.context'],
|
||||
req.environ['QUERY_STRING'].split('=')[-1])
|
||||
|
||||
def test_update_share_type_quota_using_too_old_microversion(self):
|
||||
self.mock_object(
|
||||
quota_sets.db, 'share_type_get_by_name_or_id',
|
||||
mock.Mock(
|
||||
return_value={'id': 'fake_st_id', 'name': 'fake_st_name'}))
|
||||
req = self._get_share_type_request_object('2.38')
|
||||
body = {'quota_set': {'tenant_id': self.project_id, 'shares': 788}}
|
||||
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPBadRequest,
|
||||
self.controller.update,
|
||||
req, self.project_id, body=body)
|
||||
|
||||
quota_sets.db.share_type_get_by_name_or_id.assert_not_called()
|
||||
|
||||
def test_update_share_type_quota_for_share_networks(self):
|
||||
self.mock_object(
|
||||
quota_sets.db, 'share_type_get_by_name_or_id',
|
||||
mock.Mock(
|
||||
return_value={'id': 'fake_st_id', 'name': 'fake_st_name'}))
|
||||
req = self._get_share_type_request_object('2.39')
|
||||
body = {'quota_set': {
|
||||
'tenant_id': self.project_id, 'share_networks': 788,
|
||||
}}
|
||||
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPBadRequest,
|
||||
self.controller.update,
|
||||
req, self.project_id, body=body)
|
||||
|
||||
quota_sets.db.share_type_get_by_name_or_id.assert_called_once_with(
|
||||
req.environ['manila.context'],
|
||||
req.environ['QUERY_STRING'].split('=')[-1])
|
||||
|
||||
@ddt.data(-2, 'foo', {1: 2}, [1])
|
||||
def test_update_quota_with_invalid_value(self, value):
|
||||
|
@ -384,6 +615,46 @@ class QuotaSetsControllerTest(test.TestCase):
|
|||
REQ_WITH_USER.environ['manila.context'], self.resource_name,
|
||||
'delete')
|
||||
|
||||
def test_delete_share_type_quota(self):
|
||||
req = self._get_share_type_request_object('2.39')
|
||||
self.mock_object(quota_sets.QUOTAS, 'destroy_all_by_project')
|
||||
self.mock_object(quota_sets.QUOTAS, 'destroy_all_by_project_and_user')
|
||||
mock_delete_st_quotas = self.mock_object(
|
||||
quota_sets.QUOTAS, 'destroy_all_by_project_and_share_type')
|
||||
self.mock_object(
|
||||
quota_sets.db, 'share_type_get_by_name_or_id',
|
||||
mock.Mock(
|
||||
return_value={'id': 'fake_st_id', 'name': 'fake_st_name'}))
|
||||
|
||||
result = self.controller.delete(req, self.project_id)
|
||||
|
||||
self.assertEqual(utils.IsAMatcher(webob.response.Response), result)
|
||||
self.assertTrue(hasattr(result, 'status_code'))
|
||||
self.assertEqual(202, result.status_code)
|
||||
mock_delete_st_quotas.assert_called_once_with(
|
||||
req.environ['manila.context'], self.project_id, 'fake_st_id')
|
||||
quota_sets.QUOTAS.destroy_all_by_project.assert_not_called()
|
||||
quota_sets.QUOTAS.destroy_all_by_project_and_user.assert_not_called()
|
||||
self.mock_policy_check.assert_called_once_with(
|
||||
req.environ['manila.context'], self.resource_name, 'delete')
|
||||
quota_sets.db.share_type_get_by_name_or_id.assert_called_once_with(
|
||||
req.environ['manila.context'],
|
||||
req.environ['QUERY_STRING'].split('=')[-1])
|
||||
|
||||
def test_delete_share_type_quota_using_too_old_microversion(self):
|
||||
self.mock_object(
|
||||
quota_sets.db, 'share_type_get_by_name_or_id',
|
||||
mock.Mock(
|
||||
return_value={'id': 'fake_st_id', 'name': 'fake_st_name'}))
|
||||
req = self._get_share_type_request_object('2.38')
|
||||
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPBadRequest,
|
||||
self.controller.delete,
|
||||
req, self.project_id)
|
||||
|
||||
quota_sets.db.share_type_get_by_name_or_id.assert_not_called()
|
||||
|
||||
def test_delete_not_authorized(self):
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPForbidden,
|
||||
|
|
|
@ -2396,3 +2396,73 @@ class MessagesTableChecks(BaseMigrationChecks):
|
|||
def check_downgrade(self, engine):
|
||||
self.test_case.assertRaises(sa_exc.NoSuchTableError, utils.load_table,
|
||||
'messages', engine)
|
||||
|
||||
|
||||
@map_to_migration('b516de97bfee')
|
||||
class ProjectShareTypesQuotasChecks(BaseMigrationChecks):
|
||||
new_table_name = 'project_share_type_quotas'
|
||||
usages_table = 'quota_usages'
|
||||
reservations_table = 'reservations'
|
||||
st_record_id = uuidutils.generate_uuid()
|
||||
|
||||
def setup_upgrade_data(self, engine):
|
||||
# Create share type
|
||||
self.st_data = {
|
||||
'id': self.st_record_id,
|
||||
'name': uuidutils.generate_uuid(),
|
||||
'deleted': "False",
|
||||
}
|
||||
st_table = utils.load_table('share_types', engine)
|
||||
engine.execute(st_table.insert(self.st_data))
|
||||
|
||||
def check_upgrade(self, engine, data):
|
||||
# Create share type quota
|
||||
self.quota_data = {
|
||||
'project_id': 'x' * 255,
|
||||
'resource': 'y' * 255,
|
||||
'hard_limit': 987654321,
|
||||
'created_at': datetime.datetime(2017, 4, 11, 18, 5, 58),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
'deleted': 0,
|
||||
'share_type_id': self.st_record_id,
|
||||
}
|
||||
new_table = utils.load_table(self.new_table_name, engine)
|
||||
engine.execute(new_table.insert(self.quota_data))
|
||||
|
||||
# Create usage record
|
||||
self.usages_data = {
|
||||
'project_id': 'x' * 255,
|
||||
'user_id': None,
|
||||
'share_type_id': self.st_record_id,
|
||||
'resource': 'y' * 255,
|
||||
'in_use': 13,
|
||||
'reserved': 15,
|
||||
}
|
||||
usages_table = utils.load_table(self.usages_table, engine)
|
||||
engine.execute(usages_table.insert(self.usages_data))
|
||||
|
||||
# Create reservation record
|
||||
self.reservations_data = {
|
||||
'uuid': uuidutils.generate_uuid(),
|
||||
'usage_id': 1,
|
||||
'project_id': 'x' * 255,
|
||||
'user_id': None,
|
||||
'share_type_id': self.st_record_id,
|
||||
'resource': 'y' * 255,
|
||||
'delta': 13,
|
||||
'expire': datetime.datetime(2399, 4, 11, 18, 5, 58),
|
||||
}
|
||||
reservations_table = utils.load_table(self.reservations_table, engine)
|
||||
engine.execute(reservations_table.insert(self.reservations_data))
|
||||
|
||||
def check_downgrade(self, engine):
|
||||
self.test_case.assertRaises(
|
||||
sa_exc.NoSuchTableError,
|
||||
utils.load_table, self.new_table_name, engine)
|
||||
for table_name in (self.usages_table, self.reservations_table):
|
||||
table = utils.load_table(table_name, engine)
|
||||
db_result = engine.execute(table.select())
|
||||
self.test_case.assertGreater(db_result.rowcount, 0)
|
||||
for row in db_result:
|
||||
self.test_case.assertFalse(hasattr(row, 'share_type_id'))
|
||||
|
|
|
@ -39,6 +39,7 @@ def fake_share(**kwargs):
|
|||
'is_busy': False,
|
||||
'share_group_id': None,
|
||||
'instance': {
|
||||
'id': 'fake_share_instance_id',
|
||||
'host': 'fakehost',
|
||||
'share_type_id': '1',
|
||||
},
|
||||
|
@ -61,6 +62,7 @@ def fake_share_instance(base_share=None, **kwargs):
|
|||
'host': 'fakehost',
|
||||
'share_network_id': 'fakesharenetworkid',
|
||||
'share_server_id': 'fakeshareserverid',
|
||||
'share_type_id': '1',
|
||||
}
|
||||
|
||||
for attr in models.ShareInstance._proxified_properties:
|
||||
|
@ -153,7 +155,10 @@ def fake_snapshot_instance(base_snapshot=None, as_primitive=False, **kwargs):
|
|||
'provider_location': 'i_live_here_actually',
|
||||
'share_name': 'fakename',
|
||||
'share_id': 'fakeshareinstanceid',
|
||||
'share_instance': {'share_id': 'fakeshareid', },
|
||||
'share_instance': {
|
||||
'share_id': 'fakeshareid',
|
||||
'share_type_id': '1',
|
||||
},
|
||||
'share_instance_id': 'fakeshareinstanceid',
|
||||
'deleted': False,
|
||||
'updated_at': datetime.datetime(2016, 3, 21, 0, 5, 58),
|
||||
|
|
|
@ -677,7 +677,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
share_data['display_description']
|
||||
)
|
||||
quota.QUOTAS.reserve.assert_called_once_with(
|
||||
self.context, shares=1, gigabytes=share_data['size'])
|
||||
self.context, share_type_id=None,
|
||||
shares=1, gigabytes=share_data['size'])
|
||||
|
||||
@ddt.data(exception.QuotaError, exception.InvalidShare)
|
||||
def test_create_share_error_on_quota_commit(self, expected_exception):
|
||||
|
@ -700,8 +701,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
share_data['display_description']
|
||||
)
|
||||
|
||||
quota.QUOTAS.rollback.assert_called_once_with(self.context,
|
||||
reservation)
|
||||
quota.QUOTAS.rollback.assert_called_once_with(
|
||||
self.context, reservation, share_type_id=None)
|
||||
db_api.share_delete.assert_called_once_with(self.context, share['id'])
|
||||
|
||||
def test_create_share_instance_with_host_and_az(self):
|
||||
|
@ -1033,9 +1034,10 @@ class ShareAPITestCase(test.TestCase):
|
|||
share_api.policy.check_policy.assert_called_once_with(
|
||||
self.context, 'share', 'create_snapshot', share)
|
||||
quota.QUOTAS.reserve.assert_called_once_with(
|
||||
self.context, snapshots=1, snapshot_gigabytes=1)
|
||||
self.context, share_type_id=None,
|
||||
snapshot_gigabytes=1, snapshots=1)
|
||||
quota.QUOTAS.commit.assert_called_once_with(
|
||||
self.context, 'reservation')
|
||||
self.context, 'reservation', share_type_id=None)
|
||||
db_api.share_snapshot_create.assert_called_once_with(
|
||||
self.context, options)
|
||||
|
||||
|
@ -1248,7 +1250,8 @@ class ShareAPITestCase(test.TestCase):
|
|||
|
||||
if reservations is not None:
|
||||
mock_quotas_rollback.assert_called_once_with(
|
||||
self.context, reservations)
|
||||
self.context, reservations,
|
||||
share_type_id=share['instance']['share_type_id'])
|
||||
else:
|
||||
self.assertFalse(mock_quotas_rollback.called)
|
||||
|
||||
|
@ -1284,6 +1287,7 @@ class ShareAPITestCase(test.TestCase):
|
|||
self.assertEqual('fake_reservations', result)
|
||||
mock_quotas_reserve.assert_called_once_with(
|
||||
self.context, project_id='fake_project', gigabytes=1,
|
||||
share_type_id=share['instance']['share_type_id'],
|
||||
user_id='fake_user')
|
||||
|
||||
def test_handle_revert_to_snapshot_quotas_quota_exceeded(self):
|
||||
|
@ -1649,9 +1653,10 @@ class ShareAPITestCase(test.TestCase):
|
|||
mock.call(self.context, 'share', 'create'),
|
||||
mock.call(self.context, 'share_snapshot', 'get_snapshot')])
|
||||
quota.QUOTAS.reserve.assert_called_once_with(
|
||||
self.context, gigabytes=1, shares=1)
|
||||
self.context, share_type_id=share_type['id'],
|
||||
gigabytes=1, shares=1)
|
||||
quota.QUOTAS.commit.assert_called_once_with(
|
||||
self.context, 'reservation')
|
||||
self.context, 'reservation', share_type_id=share_type['id'])
|
||||
|
||||
def test_create_from_snapshot_with_different_share_type(self):
|
||||
snapshot, share, share_data, request_spec = (
|
||||
|
@ -1734,12 +1739,14 @@ class ShareAPITestCase(test.TestCase):
|
|||
project_id=share['project_id'],
|
||||
shares=-1,
|
||||
gigabytes=-share['size'],
|
||||
share_type_id=None,
|
||||
user_id=share['user_id']
|
||||
)
|
||||
quota.QUOTAS.commit.assert_called_once_with(
|
||||
diff_user_context,
|
||||
mock.ANY,
|
||||
project_id=share['project_id'],
|
||||
share_type_id=None,
|
||||
user_id=share['user_id']
|
||||
)
|
||||
|
||||
|
@ -1810,6 +1817,7 @@ class ShareAPITestCase(test.TestCase):
|
|||
project_id=share['project_id'],
|
||||
shares=-1,
|
||||
gigabytes=-share['size'],
|
||||
share_type_id=None,
|
||||
user_id=share['user_id']
|
||||
)
|
||||
self.assertFalse(quota.QUOTAS.commit.called)
|
||||
|
@ -2239,6 +2247,7 @@ class ShareAPITestCase(test.TestCase):
|
|||
diff_user_context,
|
||||
project_id=share['project_id'],
|
||||
gigabytes=size_increase,
|
||||
share_type_id=None,
|
||||
user_id=share['user_id']
|
||||
)
|
||||
|
||||
|
|
|
@ -1606,7 +1606,7 @@ class ShareManagerTestCase(test.TestCase):
|
|||
def test_delete_snapshot_with_quota_error(self, quota_error):
|
||||
|
||||
share_id = 'FAKE_SHARE_ID'
|
||||
share = fakes.fake_share(id=share_id, instance={'id': 'fake_id'})
|
||||
share = fakes.fake_share(id=share_id)
|
||||
snapshot_instance = fakes.fake_snapshot_instance(
|
||||
share_id=share_id, share=share, name='fake_snapshot')
|
||||
snapshot = fakes.fake_snapshot(
|
||||
|
@ -1648,7 +1648,8 @@ class ShareManagerTestCase(test.TestCase):
|
|||
self.assertTrue(manager.QUOTAS.reserve.called)
|
||||
quota.QUOTAS.reserve.assert_called_once_with(
|
||||
mock.ANY, project_id=self.context.project_id, snapshots=-1,
|
||||
snapshot_gigabytes=-snapshot['size'], user_id=snapshot['user_id'])
|
||||
snapshot_gigabytes=-snapshot['size'], user_id=snapshot['user_id'],
|
||||
share_type_id=share['instance']['share_type_id'])
|
||||
self.assertEqual(not quota_error, quota_commit_call.called)
|
||||
self.assertEqual(quota_error, mock_exception_log.called)
|
||||
self.assertEqual(expected_exc_count, mock_exception_log.call_count)
|
||||
|
@ -1660,7 +1661,7 @@ class ShareManagerTestCase(test.TestCase):
|
|||
raise exception.QuotaError(code='500')
|
||||
|
||||
share_id = 'FAKE_SHARE_ID'
|
||||
share = fakes.fake_share(id=share_id, instance={'id': 'fake_id'})
|
||||
share = fakes.fake_share(id=share_id)
|
||||
snapshot_instance = fakes.fake_snapshot_instance(
|
||||
share_id=share_id, share=share, name='fake_snapshot')
|
||||
snapshot = fakes.fake_snapshot(
|
||||
|
@ -3038,7 +3039,8 @@ class ShareManagerTestCase(test.TestCase):
|
|||
mock.ANY,
|
||||
reservations,
|
||||
project_id=six.text_type(share['project_id']),
|
||||
user_id=six.text_type(share['user_id'])
|
||||
user_id=six.text_type(share['user_id']),
|
||||
share_type_id=None,
|
||||
)
|
||||
|
||||
@mock.patch('manila.tests.fake_notifier.FakeNotifier._notify')
|
||||
|
@ -3076,7 +3078,7 @@ class ShareManagerTestCase(test.TestCase):
|
|||
)
|
||||
quota.QUOTAS.commit.assert_called_once_with(
|
||||
mock.ANY, reservations, project_id=share['project_id'],
|
||||
user_id=share['user_id'])
|
||||
user_id=share['user_id'], share_type_id=None)
|
||||
manager.db.share_update.assert_called_once_with(
|
||||
mock.ANY, share_id, shr_update
|
||||
)
|
||||
|
@ -3103,6 +3105,7 @@ class ShareManagerTestCase(test.TestCase):
|
|||
mock.ANY,
|
||||
project_id=six.text_type(share['project_id']),
|
||||
user_id=six.text_type(share['user_id']),
|
||||
share_type_id=None,
|
||||
gigabytes=new_size - size
|
||||
)
|
||||
self.assertTrue(self.share_manager.db.share_update.called)
|
||||
|
@ -3140,11 +3143,11 @@ class ShareManagerTestCase(test.TestCase):
|
|||
)
|
||||
quota.QUOTAS.reserve.assert_called_once_with(
|
||||
mock.ANY, gigabytes=-size_decrease, project_id=share['project_id'],
|
||||
user_id=share['user_id']
|
||||
share_type_id=None, user_id=share['user_id'],
|
||||
)
|
||||
quota.QUOTAS.rollback.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, project_id=share['project_id'],
|
||||
user_id=share['user_id']
|
||||
share_type_id=None, user_id=share['user_id'],
|
||||
)
|
||||
self.assertTrue(self.share_manager.db.share_get.called)
|
||||
|
||||
|
@ -3184,11 +3187,11 @@ class ShareManagerTestCase(test.TestCase):
|
|||
|
||||
quota.QUOTAS.reserve.assert_called_once_with(
|
||||
mock.ANY, gigabytes=-size_decrease, project_id=share['project_id'],
|
||||
user_id=share['user_id']
|
||||
share_type_id=None, user_id=share['user_id'],
|
||||
)
|
||||
quota.QUOTAS.commit.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, project_id=share['project_id'],
|
||||
user_id=share['user_id']
|
||||
share_type_id=None, user_id=share['user_id'],
|
||||
)
|
||||
manager.db.share_update.assert_called_once_with(
|
||||
mock.ANY, share_id, shr_update
|
||||
|
@ -5508,10 +5511,12 @@ class ShareManagerTestCase(test.TestCase):
|
|||
|
||||
share_id = 'fake_share_id'
|
||||
share = fakes.fake_share(
|
||||
id=share_id, instance={'id': 'fake_instance_id'},
|
||||
id=share_id, instance={'id': 'fake_instance_id',
|
||||
'share_type_id': 'fake_share_type_id'},
|
||||
project_id='fake_project', user_id='fake_user', size=2)
|
||||
snapshot_instance = fakes.fake_snapshot_instance(
|
||||
share_id=share_id, share=share, name='fake_snapshot')
|
||||
share_id=share_id, share=share, name='fake_snapshot',
|
||||
share_instance=share['instance'])
|
||||
snapshot = fakes.fake_snapshot(
|
||||
id='fake_snapshot_id', share_id=share_id, share=share,
|
||||
instance=snapshot_instance, project_id='fake_project',
|
||||
|
@ -5543,7 +5548,9 @@ class ShareManagerTestCase(test.TestCase):
|
|||
if reservations:
|
||||
mock_quotas_commit.assert_called_once_with(
|
||||
mock.ANY, reservations, project_id='fake_project',
|
||||
user_id='fake_user')
|
||||
user_id='fake_user',
|
||||
share_type_id=(
|
||||
snapshot_instance['share_instance']['share_type_id']))
|
||||
else:
|
||||
self.assertFalse(mock_quotas_commit.called)
|
||||
|
||||
|
@ -5567,10 +5574,12 @@ class ShareManagerTestCase(test.TestCase):
|
|||
|
||||
share_id = 'fake_share_id'
|
||||
share = fakes.fake_share(
|
||||
id=share_id, instance={'id': 'fake_instance_id'},
|
||||
id=share_id, instance={'id': 'fake_instance_id',
|
||||
'share_type_id': 'fake_share_type_id'},
|
||||
project_id='fake_project', user_id='fake_user', size=2)
|
||||
snapshot_instance = fakes.fake_snapshot_instance(
|
||||
share_id=share_id, share=share, name='fake_snapshot')
|
||||
share_id=share_id, share=share, name='fake_snapshot',
|
||||
share_instance=share['instance'])
|
||||
snapshot = fakes.fake_snapshot(
|
||||
id='fake_snapshot_id', share_id=share_id, share=share,
|
||||
instance=snapshot_instance, project_id='fake_project',
|
||||
|
@ -5607,7 +5616,9 @@ class ShareManagerTestCase(test.TestCase):
|
|||
if reservations:
|
||||
mock_quotas_rollback.assert_called_once_with(
|
||||
mock.ANY, reservations, project_id='fake_project',
|
||||
user_id='fake_user')
|
||||
user_id='fake_user',
|
||||
share_type_id=(
|
||||
snapshot_instance['share_instance']['share_type_id']))
|
||||
else:
|
||||
self.assertFalse(mock_quotas_rollback.called)
|
||||
|
||||
|
@ -5822,7 +5833,7 @@ class ShareManagerTestCase(test.TestCase):
|
|||
if reservations:
|
||||
mock_quotas_commit.assert_called_once_with(
|
||||
mock.ANY, reservations, project_id='fake_project',
|
||||
user_id='fake_user')
|
||||
user_id='fake_user', share_type_id=None)
|
||||
else:
|
||||
self.assertFalse(mock_quotas_commit.called)
|
||||
|
||||
|
@ -5851,10 +5862,12 @@ class ShareManagerTestCase(test.TestCase):
|
|||
snapshot_instances = [snapshot['instance'], snapshot_instance]
|
||||
active_replica = fake_replica(
|
||||
id='rid1', share_id=share_id, host=self.share_manager.host,
|
||||
replica_state=constants.REPLICA_STATE_ACTIVE, as_primitive=False)
|
||||
replica_state=constants.REPLICA_STATE_ACTIVE, as_primitive=False,
|
||||
share_type_id='fake_share_type_id')
|
||||
replica = fake_replica(
|
||||
id='rid2', share_id=share_id, host='secondary',
|
||||
replica_state=constants.REPLICA_STATE_IN_SYNC, as_primitive=False)
|
||||
replica_state=constants.REPLICA_STATE_IN_SYNC, as_primitive=False,
|
||||
share_type_id='fake_share_type_id')
|
||||
replicas = [active_replica, replica]
|
||||
access_rules = []
|
||||
self.mock_object(
|
||||
|
@ -5893,7 +5906,7 @@ class ShareManagerTestCase(test.TestCase):
|
|||
if reservations:
|
||||
mock_quotas_rollback.assert_called_once_with(
|
||||
mock.ANY, reservations, project_id='fake_project',
|
||||
user_id='fake_user')
|
||||
user_id='fake_user', share_type_id=replica['share_type_id'])
|
||||
else:
|
||||
self.assertFalse(mock_quotas_rollback.called)
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -30,7 +30,7 @@ ShareGroup = [
|
|||
help="The minimum api microversion is configured to be the "
|
||||
"value of the minimum microversion supported by Manila."),
|
||||
cfg.StrOpt("max_api_microversion",
|
||||
default="2.38",
|
||||
default="2.39",
|
||||
help="The maximum api microversion is configured to be the "
|
||||
"value of the latest microversion supported by Manila."),
|
||||
cfg.StrOpt("region",
|
||||
|
|
|
@ -852,11 +852,23 @@ class SharesV2Client(shares_client.SharesClient):
|
|||
|
||||
###############
|
||||
|
||||
def _get_quotas_url(self, version):
|
||||
@staticmethod
|
||||
def _get_quotas_url(version):
|
||||
if utils.is_microversion_gt(version, "2.6"):
|
||||
return 'quota-sets'
|
||||
return 'os-quota-sets'
|
||||
|
||||
@staticmethod
|
||||
def _get_quotas_url_arguments_as_str(user_id=None, share_type=None):
|
||||
args_str = ''
|
||||
if not (user_id is None or share_type is None):
|
||||
args_str = "?user_id=%s&share_type=%s" % (user_id, share_type)
|
||||
elif user_id is not None:
|
||||
args_str = "?user_id=%s" % user_id
|
||||
elif share_type is not None:
|
||||
args_str = "?share_type=%s" % share_type
|
||||
return args_str
|
||||
|
||||
def default_quotas(self, tenant_id, url=None, version=LATEST_MICROVERSION):
|
||||
if url is None:
|
||||
url = self._get_quotas_url(version)
|
||||
|
@ -865,48 +877,44 @@ class SharesV2Client(shares_client.SharesClient):
|
|||
self.expected_success(200, resp.status)
|
||||
return self._parse_resp(body)
|
||||
|
||||
def show_quotas(self, tenant_id, user_id=None, url=None,
|
||||
def show_quotas(self, tenant_id, user_id=None, share_type=None, url=None,
|
||||
version=LATEST_MICROVERSION):
|
||||
if url is None:
|
||||
url = self._get_quotas_url(version)
|
||||
url += '/%s' % tenant_id
|
||||
if user_id is not None:
|
||||
url += "?user_id=%s" % user_id
|
||||
url += self._get_quotas_url_arguments_as_str(user_id, share_type)
|
||||
resp, body = self.get(url, version=version)
|
||||
self.expected_success(200, resp.status)
|
||||
return self._parse_resp(body)
|
||||
|
||||
def reset_quotas(self, tenant_id, user_id=None, url=None,
|
||||
def reset_quotas(self, tenant_id, user_id=None, share_type=None, url=None,
|
||||
version=LATEST_MICROVERSION):
|
||||
if url is None:
|
||||
url = self._get_quotas_url(version)
|
||||
url += '/%s' % tenant_id
|
||||
if user_id is not None:
|
||||
url += "?user_id=%s" % user_id
|
||||
url += self._get_quotas_url_arguments_as_str(user_id, share_type)
|
||||
resp, body = self.delete(url, version=version)
|
||||
self.expected_success(202, resp.status)
|
||||
return body
|
||||
|
||||
def detail_quotas(self, tenant_id, user_id=None, url=None,
|
||||
def detail_quotas(self, tenant_id, user_id=None, share_type=None, url=None,
|
||||
version=LATEST_MICROVERSION):
|
||||
if url is None:
|
||||
url = self._get_quotas_url(version)
|
||||
url += '/%s/detail' % tenant_id
|
||||
if user_id is not None:
|
||||
url += "?user_id=%s" % user_id
|
||||
url += self._get_quotas_url_arguments_as_str(user_id, share_type)
|
||||
resp, body = self.get(url, version=version)
|
||||
self.expected_success(200, resp.status)
|
||||
return self._parse_resp(body)
|
||||
|
||||
def update_quotas(self, tenant_id, user_id=None, shares=None,
|
||||
snapshots=None, gigabytes=None, snapshot_gigabytes=None,
|
||||
share_networks=None, force=True, url=None,
|
||||
version=LATEST_MICROVERSION):
|
||||
share_networks=None, force=True, share_type=None,
|
||||
url=None, version=LATEST_MICROVERSION):
|
||||
if url is None:
|
||||
url = self._get_quotas_url(version)
|
||||
url += '/%s' % tenant_id
|
||||
if user_id is not None:
|
||||
url += "?user_id=%s" % user_id
|
||||
url += self._get_quotas_url_arguments_as_str(user_id, share_type)
|
||||
|
||||
put_body = {"tenant_id": tenant_id}
|
||||
if force:
|
||||
|
|
|
@ -13,7 +13,10 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ddt
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
from testtools import testcase as tc
|
||||
|
||||
from manila_tempest_tests.tests.api import base
|
||||
|
@ -21,6 +24,7 @@ from manila_tempest_tests.tests.api import base
|
|||
CONF = config.CONF
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class SharesAdminQuotasTest(base.BaseSharesAdminTest):
|
||||
|
||||
@classmethod
|
||||
|
@ -60,7 +64,37 @@ class SharesAdminQuotasTest(base.BaseSharesAdminTest):
|
|||
self.assertGreater(int(quotas["snapshots"]), -2)
|
||||
self.assertGreater(int(quotas["share_networks"]), -2)
|
||||
|
||||
@ddt.data(
|
||||
('id', True),
|
||||
('name', False),
|
||||
)
|
||||
@ddt.unpack
|
||||
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
|
||||
@base.skip_if_microversion_lt("2.39")
|
||||
def test_show_share_type_quotas(self, share_type_key, is_st_public):
|
||||
# Create share type
|
||||
share_type = self.create_share_type(
|
||||
data_utils.rand_name("tempest-manila"),
|
||||
is_public=is_st_public,
|
||||
cleanup_in_class=False,
|
||||
extra_specs=self.add_extra_specs_to_dict(),
|
||||
)
|
||||
if 'share_type' in share_type:
|
||||
share_type = share_type['share_type']
|
||||
|
||||
# Get current project quotas
|
||||
p_quotas = self.shares_v2_client.show_quotas(self.tenant_id)
|
||||
|
||||
# Get current quotas
|
||||
st_quotas = self.shares_v2_client.show_quotas(
|
||||
self.tenant_id, share_type=share_type[share_type_key])
|
||||
|
||||
# Share type quotas have values equal to project's
|
||||
for key in ('shares', 'gigabytes', 'snapshots', 'snapshot_gigabytes'):
|
||||
self.assertEqual(st_quotas[key], p_quotas[key])
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
|
||||
|
||||
force_tenant_isolation = True
|
||||
|
@ -101,6 +135,47 @@ class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
|
|||
self.tenant_id, self.user_id, shares=new_quota)
|
||||
self.assertEqual(new_quota, int(updated["shares"]))
|
||||
|
||||
def _create_share_type(self):
|
||||
share_type = self.create_share_type(
|
||||
data_utils.rand_name("tempest-manila"),
|
||||
cleanup_in_class=False,
|
||||
client=self.shares_v2_client,
|
||||
extra_specs=self.add_extra_specs_to_dict(),
|
||||
)
|
||||
if 'share_type' in share_type:
|
||||
share_type = share_type['share_type']
|
||||
return share_type
|
||||
|
||||
@ddt.data(
|
||||
('id', True),
|
||||
('name', False),
|
||||
)
|
||||
@ddt.unpack
|
||||
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
|
||||
@base.skip_if_microversion_lt("2.39")
|
||||
def test_update_share_type_quota(self, share_type_key, is_st_public):
|
||||
share_type = self._create_share_type()
|
||||
|
||||
# Get current quotas
|
||||
quotas = self.client.show_quotas(
|
||||
self.tenant_id, share_type=share_type[share_type_key])
|
||||
|
||||
# Update quotas
|
||||
for q in ('shares', 'gigabytes', 'snapshots', 'snapshot_gigabytes'):
|
||||
new_quota = int(quotas[q]) - 1
|
||||
|
||||
# Set new quota
|
||||
updated = self.client.update_quotas(
|
||||
self.tenant_id, share_type=share_type[share_type_key],
|
||||
**{q: new_quota})
|
||||
self.assertEqual(new_quota, int(updated[q]))
|
||||
|
||||
current_quotas = self.client.show_quotas(
|
||||
self.tenant_id, share_type=share_type[share_type_key])
|
||||
|
||||
for q in ('shares', 'gigabytes', 'snapshots', 'snapshot_gigabytes'):
|
||||
self.assertEqual(int(quotas[q]) - 1, current_quotas[q])
|
||||
|
||||
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
|
||||
def test_update_tenant_quota_snapshots(self):
|
||||
# get current quotas
|
||||
|
@ -244,6 +319,51 @@ class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
|
|||
self.assertEqual(int(default["share_networks"]),
|
||||
int(reseted["share_networks"]))
|
||||
|
||||
@ddt.data(
|
||||
('id', True),
|
||||
('name', False),
|
||||
)
|
||||
@ddt.unpack
|
||||
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
|
||||
@base.skip_if_microversion_lt("2.39")
|
||||
def test_reset_share_type_quotas(self, share_type_key, is_st_public):
|
||||
share_type = self._create_share_type()
|
||||
|
||||
# get default_quotas
|
||||
default_quotas = self.client.default_quotas(self.tenant_id)
|
||||
|
||||
# set new quota for project
|
||||
updated_p_quota = self.client.update_quotas(
|
||||
self.tenant_id,
|
||||
shares=int(default_quotas['shares']) + 5,
|
||||
snapshots=int(default_quotas['snapshots']) + 5,
|
||||
gigabytes=int(default_quotas['gigabytes']) + 5,
|
||||
snapshot_gigabytes=int(default_quotas['snapshot_gigabytes']) + 5)
|
||||
|
||||
# set new quota for project
|
||||
self.client.update_quotas(
|
||||
self.tenant_id,
|
||||
share_type=share_type[share_type_key],
|
||||
shares=int(default_quotas['shares']) + 3,
|
||||
snapshots=int(default_quotas['snapshots']) + 3,
|
||||
gigabytes=int(default_quotas['gigabytes']) + 3,
|
||||
snapshot_gigabytes=int(default_quotas['snapshot_gigabytes']) + 3)
|
||||
|
||||
# reset share type quotas
|
||||
self.client.reset_quotas(
|
||||
self.tenant_id, share_type=share_type[share_type_key])
|
||||
|
||||
# verify quotas
|
||||
current_p_quota = self.client.show_quotas(self.tenant_id)
|
||||
current_st_quota = self.client.show_quotas(
|
||||
self.tenant_id, share_type=share_type[share_type_key])
|
||||
for key in ('shares', 'snapshots', 'gigabytes', 'snapshot_gigabytes'):
|
||||
self.assertEqual(updated_p_quota[key], current_p_quota[key])
|
||||
|
||||
# Default share type quotas are current project quotas
|
||||
self.assertNotEqual(default_quotas[key], current_st_quota[key])
|
||||
self.assertEqual(current_p_quota[key], current_st_quota[key])
|
||||
|
||||
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
|
||||
def test_unlimited_quota_for_shares(self):
|
||||
self.client.update_quotas(self.tenant_id, shares=-1)
|
||||
|
@ -329,3 +449,95 @@ class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
|
|||
quotas = self.client.show_quotas(self.tenant_id, self.user_id)
|
||||
|
||||
self.assertEqual(-1, quotas.get('share_networks'))
|
||||
|
||||
@ddt.data(11, -1)
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
def test_update_user_quotas_bigger_than_project_quota(self, user_quota):
|
||||
self.client.update_quotas(self.tenant_id, shares=10)
|
||||
self.client.update_quotas(
|
||||
self.tenant_id, user_id=self.user_id, force=True,
|
||||
shares=user_quota)
|
||||
|
||||
@ddt.data(11, -1)
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
@base.skip_if_microversion_lt("2.39")
|
||||
def test_update_share_type_quotas_bigger_than_project_quota(self, st_q):
|
||||
share_type = self._create_share_type()
|
||||
self.client.update_quotas(self.tenant_id, shares=10)
|
||||
|
||||
self.client.update_quotas(
|
||||
self.tenant_id, share_type=share_type['name'], force=True,
|
||||
shares=st_q)
|
||||
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
@base.skip_if_microversion_lt("2.39")
|
||||
def test_set_share_type_quota_bigger_than_users_quota(self):
|
||||
share_type = self._create_share_type()
|
||||
self.client.update_quotas(self.tenant_id, force=False, shares=13)
|
||||
self.client.update_quotas(
|
||||
self.tenant_id, user_id=self.user_id, force=False, shares=11)
|
||||
|
||||
# Share type quota does not depend on user's quota, so we should be
|
||||
# able to update it.
|
||||
self.client.update_quotas(
|
||||
self.tenant_id, share_type=share_type['name'], force=False,
|
||||
shares=12)
|
||||
|
||||
@tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
|
||||
@base.skip_if_microversion_lt("2.39")
|
||||
def test_quotas_usages(self):
|
||||
# Create share types
|
||||
st_1, st_2 = (self._create_share_type() for i in (1, 2))
|
||||
|
||||
# Set quotas for project, user and both share types
|
||||
self.client.update_quotas(self.tenant_id, shares=3, gigabytes=10)
|
||||
self.client.update_quotas(
|
||||
self.tenant_id, user_id=self.user_id, shares=2, gigabytes=7)
|
||||
for st in (st_1['id'], st_2['name']):
|
||||
self.client.update_quotas(
|
||||
self.tenant_id, share_type=st, shares=2, gigabytes=4)
|
||||
|
||||
# Create share, 4Gb, st1 - ok
|
||||
share_1 = self.create_share(
|
||||
size=4, share_type_id=st_1['id'], client=self.client,
|
||||
cleanup_in_class=False)
|
||||
|
||||
# Try create shares twice, failing on user and share type quotas
|
||||
for size, st_id in ((3, st_1['id']), (4, st_2['id'])):
|
||||
self.assertRaises(
|
||||
lib_exc.OverLimit,
|
||||
self.create_share,
|
||||
size=size, share_type_id=st_id, client=self.client,
|
||||
cleanup_in_class=False)
|
||||
|
||||
# Create share, 3Gb, st2 - ok
|
||||
share_2 = self.create_share(
|
||||
size=3, share_type_id=st_2['id'], client=self.client,
|
||||
cleanup_in_class=False)
|
||||
|
||||
# Check quota usages
|
||||
for g_l, g_use, s_l, s_use, kwargs in (
|
||||
(10, 7, 3, 2, {}),
|
||||
(7, 7, 2, 2, {'user_id': self.user_id}),
|
||||
(4, 4, 2, 1, {'share_type': st_1['id']}),
|
||||
(4, 3, 2, 1, {'share_type': st_2['name']})):
|
||||
quotas = self.client.detail_quotas(
|
||||
tenant_id=self.tenant_id, **kwargs)
|
||||
self.assertEqual(0, quotas['gigabytes']['reserved'])
|
||||
self.assertEqual(g_l, quotas['gigabytes']['limit'])
|
||||
self.assertEqual(g_use, quotas['gigabytes']['in_use'])
|
||||
self.assertEqual(0, quotas['shares']['reserved'])
|
||||
self.assertEqual(s_l, quotas['shares']['limit'])
|
||||
self.assertEqual(s_use, quotas['shares']['in_use'])
|
||||
|
||||
# Delete shares and then check usages
|
||||
for share_id in (share_1['id'], share_2['id']):
|
||||
self.client.delete_share(share_id)
|
||||
self.client.wait_for_resource_deletion(share_id=share_id)
|
||||
for kwargs in ({}, {'share_type': st_1['name']},
|
||||
{'user_id': self.user_id}, {'share_type': st_2['id']}):
|
||||
quotas = self.client.detail_quotas(
|
||||
tenant_id=self.tenant_id, **kwargs)
|
||||
for key in ('shares', 'gigabytes'):
|
||||
self.assertEqual(0, quotas[key]['reserved'])
|
||||
self.assertEqual(0, quotas[key]['in_use'])
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
import ddt
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
from testtools import testcase as tc
|
||||
|
||||
|
@ -117,6 +118,7 @@ class SharesAdminQuotasNegativeTest(base.BaseSharesAdminTest):
|
|||
client.update_quotas,
|
||||
client.tenant_id,
|
||||
client.user_id,
|
||||
force=False,
|
||||
shares=bigger_value)
|
||||
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
|
@ -132,6 +134,7 @@ class SharesAdminQuotasNegativeTest(base.BaseSharesAdminTest):
|
|||
client.update_quotas,
|
||||
client.tenant_id,
|
||||
client.user_id,
|
||||
force=False,
|
||||
snapshots=bigger_value)
|
||||
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
|
@ -147,6 +150,7 @@ class SharesAdminQuotasNegativeTest(base.BaseSharesAdminTest):
|
|||
client.update_quotas,
|
||||
client.tenant_id,
|
||||
client.user_id,
|
||||
force=False,
|
||||
gigabytes=bigger_value)
|
||||
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
|
@ -162,6 +166,7 @@ class SharesAdminQuotasNegativeTest(base.BaseSharesAdminTest):
|
|||
client.update_quotas,
|
||||
client.tenant_id,
|
||||
client.user_id,
|
||||
force=False,
|
||||
snapshot_gigabytes=bigger_value)
|
||||
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
|
@ -177,6 +182,7 @@ class SharesAdminQuotasNegativeTest(base.BaseSharesAdminTest):
|
|||
client.update_quotas,
|
||||
client.tenant_id,
|
||||
client.user_id,
|
||||
force=False,
|
||||
share_networks=bigger_value)
|
||||
|
||||
@ddt.data(
|
||||
|
@ -215,3 +221,98 @@ class SharesAdminQuotasNegativeTest(base.BaseSharesAdminTest):
|
|||
self.shares_v2_client.tenant_id,
|
||||
version=version, url=url,
|
||||
)
|
||||
|
||||
@ddt.data('show', 'reset', 'update')
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
@base.skip_if_microversion_lt("2.39")
|
||||
def test_share_type_quotas_using_nonexistent_share_type(self, op):
|
||||
client = self.get_client_with_isolated_creds(client_version='2')
|
||||
|
||||
kwargs = {"share_type": "fake_nonexistent_share_type"}
|
||||
if op == 'update':
|
||||
tenant_quotas = client.show_quotas(client.tenant_id)
|
||||
kwargs['shares'] = tenant_quotas['shares']
|
||||
|
||||
self.assertRaises(
|
||||
lib_exc.NotFound,
|
||||
getattr(client, op + '_quotas'),
|
||||
client.tenant_id,
|
||||
**kwargs)
|
||||
|
||||
def _create_share_type(self):
|
||||
share_type = self.create_share_type(
|
||||
data_utils.rand_name("tempest-manila"),
|
||||
cleanup_in_class=False,
|
||||
client=self.shares_v2_client,
|
||||
extra_specs=self.add_extra_specs_to_dict(),
|
||||
)
|
||||
if 'share_type' in share_type:
|
||||
share_type = share_type['share_type']
|
||||
return share_type
|
||||
|
||||
@ddt.data('id', 'name')
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
@base.skip_if_microversion_lt("2.39")
|
||||
def test_try_update_share_type_quota_for_share_networks(self, key):
|
||||
client = self.get_client_with_isolated_creds(client_version='2')
|
||||
share_type = self._create_share_type()
|
||||
tenant_quotas = client.show_quotas(client.tenant_id)
|
||||
|
||||
# Try to set 'share_networks' quota for share type
|
||||
self.assertRaises(
|
||||
lib_exc.BadRequest,
|
||||
client.update_quotas,
|
||||
client.tenant_id,
|
||||
share_type=share_type[key],
|
||||
share_networks=int(tenant_quotas["share_networks"]),
|
||||
)
|
||||
|
||||
@ddt.data('show', 'reset', 'update')
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
@base.skip_if_microversion_lt("2.38")
|
||||
def test_share_type_quotas_using_too_old_microversion(self, op):
|
||||
client = self.get_client_with_isolated_creds(client_version='2')
|
||||
share_type = self._create_share_type()
|
||||
kwargs = {"version": "2.38", "share_type": share_type["name"]}
|
||||
if op == 'update':
|
||||
tenant_quotas = client.show_quotas(client.tenant_id)
|
||||
kwargs['shares'] = tenant_quotas['shares']
|
||||
|
||||
self.assertRaises(
|
||||
lib_exc.BadRequest,
|
||||
getattr(client, op + '_quotas'),
|
||||
client.tenant_id,
|
||||
**kwargs)
|
||||
|
||||
@ddt.data('show', 'reset', 'update')
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
@base.skip_if_microversion_lt("2.39")
|
||||
def test_quotas_providing_share_type_and_user_id(self, op):
|
||||
client = self.get_client_with_isolated_creds(client_version='2')
|
||||
share_type = self._create_share_type()
|
||||
kwargs = {"share_type": share_type["name"], "user_id": client.user_id}
|
||||
if op == 'update':
|
||||
tenant_quotas = client.show_quotas(client.tenant_id)
|
||||
kwargs['shares'] = tenant_quotas['shares']
|
||||
|
||||
self.assertRaises(
|
||||
lib_exc.BadRequest,
|
||||
getattr(client, op + '_quotas'),
|
||||
client.tenant_id,
|
||||
**kwargs)
|
||||
|
||||
@ddt.data(11, -1)
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
@base.skip_if_microversion_lt("2.39")
|
||||
def test_update_share_type_quotas_bigger_than_project_quota(self, st_q):
|
||||
client = self.get_client_with_isolated_creds(client_version='2')
|
||||
share_type = self._create_share_type()
|
||||
client.update_quotas(client.tenant_id, shares=10)
|
||||
|
||||
self.assertRaises(
|
||||
lib_exc.BadRequest,
|
||||
client.update_quotas,
|
||||
client.tenant_id,
|
||||
share_type=share_type['name'],
|
||||
force=False,
|
||||
shares=st_q)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
features:
|
||||
- Added possibility to set quotas per share type.
|
||||
It is useful for deployments with multiple backends that
|
||||
are accessible via different share types.
|
Loading…
Reference in New Issue