Merge "Add quotas per share type"
This commit is contained in:
commit
c34ad67936
|
@ -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