Add share groups and share group snapshots quotas

"Quota" APIs now will return two new following keys:

- 'share_groups'
- 'share_group_snapshots'

For user and project, but not share type.
Default values can be configured using following config options:

- 'quota_share_groups'
- 'quota_share_group_snapshots'

APIImpact
DocImpact
Implements BluePrint add-share-groups-quota
Change-Id: I397a8e886226cb22fa50abdf2a4a938bb04c655d
This commit is contained in:
Valeriy Ponomaryov 2017-06-09 20:03:06 +03:00
parent 7a979c2f1b
commit 256b5c84ba
20 changed files with 1068 additions and 142 deletions

View File

@ -110,13 +110,14 @@ REST_API_VERSION_HISTORY = """
* 2.38 - Support IPv6 validation in allow_access API to enable IPv6 in
manila.
* 2.39 - Added share-type quotas.
* 2.40 - Added share group and share group snapshot 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.39"
_MAX_API_VERSION = "2.40"
DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@ -222,3 +222,7 @@ user documentation.
2.39
----
Added share-type quotas.
2.40
----
Added share group and share group snapshot quotas.

View File

@ -44,7 +44,7 @@ class QuotaClassSetsMixin(object):
raise webob.exc.HTTPForbidden()
return self._view_builder.detail_list(
QUOTAS.get_class_quotas(context, id), id)
req, QUOTAS.get_class_quotas(context, id), id)
@wsgi.Controller.authorize("update")
def _update(self, req, id, body):
@ -60,7 +60,7 @@ class QuotaClassSetsMixin(object):
except exception.AdminRequired:
raise webob.exc.HTTPForbidden()
return self._view_builder.detail_list(
QUOTAS.get_class_quotas(context, quota_class))
req, QUOTAS.get_class_quotas(context, quota_class))
class QuotaClassSetsControllerLegacy(QuotaClassSetsMixin, wsgi.Controller):

View File

@ -79,10 +79,20 @@ class QuotaSetsMixin(object):
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 "
"Use 2.39 or greater microversion to be able "
"to use 'share_type' quotas.")
raise webob.exc.HTTPBadRequest(explanation=msg)
@staticmethod
def _ensure_share_group_related_args_are_absent(body):
body = body.get('quota_set', body)
for key in ('share_groups', 'share_group_snapshots'):
if body.get(key):
msg = _("'%(key)s' key is not supported by this microversion. "
"Use 2.40 or greater microversion to be able "
"to use '%(key)s' quotas.") % {"key": key}
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)
@ -112,14 +122,16 @@ class QuotaSetsMixin(object):
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)
return self._view_builder.detail_list(
req, quotas, id, share_type_id)
except exception.NotAuthorized:
raise webob.exc.HTTPForbidden()
@wsgi.Controller.authorize('show')
def _defaults(self, req, id):
context = req.environ['manila.context']
return self._view_builder.detail_list(QUOTAS.get_defaults(context), id)
return self._view_builder.detail_list(
req, QUOTAS.get_defaults(context), id)
@wsgi.Controller.authorize("update")
def _update(self, req, id, body):
@ -132,6 +144,12 @@ class QuotaSetsMixin(object):
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)
body = body.get('quota_set', {})
if share_type and body.get('share_groups',
body.get('share_group_snapshots')):
msg = _("Share type quotas handle only 'shares', 'gigabytes', "
"'snapshots' and 'snapshot_gigabytes' quotas.")
raise webob.exc.HTTPBadRequest(explanation=msg)
try:
settable_quotas = QUOTAS.get_settable_quotas(
@ -140,7 +158,7 @@ class QuotaSetsMixin(object):
except exception.NotAuthorized:
raise webob.exc.HTTPForbidden()
for key, value in body.get('quota_set', {}).items():
for key, value in body.items():
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.")
@ -171,7 +189,7 @@ class QuotaSetsMixin(object):
except exception.NotAuthorized:
raise webob.exc.HTTPForbidden()
for key, value in body.get('quota_set', {}).items():
for key, value in body.items():
if key in NON_QUOTA_KEYS or (not value and value != 0):
continue
# validate whether already used and reserved exceeds the new
@ -216,6 +234,7 @@ class QuotaSetsMixin(object):
except exception.AdminRequired:
raise webob.exc.HTTPForbidden()
return self._view_builder.detail_list(
req,
self._get_quotas(
context, id, user_id=user_id, share_type_id=share_type_id),
share_type=share_type_id,
@ -262,6 +281,7 @@ 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)
self._ensure_share_group_related_args_are_absent(body)
return self._update(req, id, body)
@wsgi.Controller.api_version('1.0', '2.6')
@ -297,6 +317,8 @@ class QuotaSetsController(QuotaSetsMixin, wsgi.Controller):
def update(self, req, id, body):
if req.api_version_request < api_version.APIVersionRequest("2.39"):
self._ensure_share_type_arg_is_absent(req)
elif req.api_version_request < api_version.APIVersionRequest("2.40"):
self._ensure_share_group_related_args_are_absent(body)
return self._update(req, id, body)
@wsgi.Controller.api_version('2.7')

View File

@ -19,8 +19,11 @@ from manila.api import common
class ViewBuilder(common.ViewBuilder):
_collection_name = "quota_class_set"
_detail_version_modifiers = [
"add_share_group_quotas",
]
def detail_list(self, quota_set, quota_class=None):
def detail_list(self, request, quota_class_set, quota_class=None):
"""Detailed view of quota class set."""
keys = (
'shares',
@ -29,7 +32,17 @@ class ViewBuilder(common.ViewBuilder):
'snapshot_gigabytes',
'share_networks',
)
view = {key: quota_set.get(key) for key in keys}
view = {key: quota_class_set.get(key) for key in keys}
if quota_class:
view['id'] = quota_class
self.update_versioned_resource_dict(request, view, quota_class_set)
return {self._collection_name: view}
@common.ViewBuilder.versioned_method("2.40")
def add_share_group_quotas(self, context, view, quota_class_set):
share_groups = quota_class_set.get('share_groups')
share_group_snapshots = quota_class_set.get('share_group_snapshots')
if share_groups is not None:
view['share_groups'] = share_groups
if share_group_snapshots is not None:
view['share_group_snapshots'] = share_group_snapshots

View File

@ -19,8 +19,12 @@ from manila.api import common
class ViewBuilder(common.ViewBuilder):
_collection_name = "quota_set"
_detail_version_modifiers = [
"add_share_group_quotas",
]
def detail_list(self, quota_set, project_id=None, share_type=None):
def detail_list(self, request, quota_set, project_id=None,
share_type=None):
"""Detailed view of quota set."""
keys = (
'shares',
@ -31,6 +35,21 @@ class ViewBuilder(common.ViewBuilder):
view = {key: quota_set.get(key) for key in keys}
if project_id:
view['id'] = project_id
if not share_type:
if share_type:
# NOTE(vponomaryov): remove share groups related data for quotas
# that are share-type based.
quota_set.pop('share_groups', None)
quota_set.pop('share_group_snapshots', None)
else:
view['share_networks'] = quota_set.get('share_networks')
self.update_versioned_resource_dict(request, view, quota_set)
return {self._collection_name: view}
@common.ViewBuilder.versioned_method("2.40")
def add_share_group_quotas(self, context, view, quota_set):
share_groups = quota_set.get('share_groups')
share_group_snapshots = quota_set.get('share_group_snapshots')
if share_groups is not None:
view['share_groups'] = share_groups
if share_group_snapshots is not None:
view['share_group_snapshots'] = share_group_snapshots

View File

@ -317,12 +317,30 @@ def _sync_share_networks(context, project_id, user_id, session,
return {'share_networks': share_networks_count}
def _sync_share_groups(context, project_id, user_id, session,
share_type_id=None):
share_groups_count = count_share_groups(
context, project_id, user_id, share_type_id=share_type_id,
session=session)
return {'share_groups': share_groups_count}
def _sync_share_group_snapshots(context, project_id, user_id, session,
share_type_id=None):
share_group_snapshots_count = count_share_group_snapshots(
context, project_id, user_id, share_type_id=share_type_id,
session=session)
return {'share_group_snapshots': share_group_snapshots_count}
QUOTA_SYNC_FUNCTIONS = {
'_sync_shares': _sync_shares,
'_sync_snapshots': _sync_snapshots,
'_sync_gigabytes': _sync_gigabytes,
'_sync_snapshot_gigabytes': _sync_snapshot_gigabytes,
'_sync_share_networks': _sync_share_networks,
'_sync_share_groups': _sync_share_groups,
'_sync_share_group_snapshots': _sync_share_group_snapshots,
}
@ -4223,6 +4241,41 @@ def get_all_shares_by_share_group(context, share_group_id, session=None):
all())
@require_context
def count_share_groups(context, project_id, user_id=None,
share_type_id=None, session=None):
query = model_query(
context, models.ShareGroup,
func.count(models.ShareGroup.id),
read_deleted="no",
session=session).filter_by(project_id=project_id)
if share_type_id:
query = query.join("share_group_share_type_mappings").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]
@require_context
def count_share_group_snapshots(context, project_id, user_id=None,
share_type_id=None, session=None):
query = model_query(
context, models.ShareGroupSnapshot,
func.count(models.ShareGroupSnapshot.id),
read_deleted="no",
session=session).filter_by(project_id=project_id)
if share_type_id:
query = query.join(
"share_group"
).join(
"share_group_share_type_mappings"
).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]
@require_context
def count_share_group_snapshots_in_share_group(context, share_group_id,
session=None):

View File

@ -428,6 +428,16 @@ class ShareNetworksLimitExceeded(QuotaError):
"allowed (%(allowed)d) exceeded.")
class ShareGroupsLimitExceeded(QuotaError):
message = _(
"Maximum number of allowed share-groups is exceeded.")
class ShareGroupSnapshotsLimitExceeded(QuotaError):
message = _(
"Maximum number of allowed share-group-snapshots is exceeded.")
class GlusterfsException(ManilaException):
message = _("Unknown Gluster exception.")

View File

@ -45,6 +45,14 @@ quota_opts = [
cfg.IntOpt('quota_share_networks',
default=10,
help='Number of share-networks allowed per project.'),
cfg.IntOpt('quota_share_groups',
default=50,
help='Number of share groups allowed.'),
cfg.IntOpt('quota_share_group_snapshots',
default=50,
help='Number of share group snapshots allowed.'),
cfg.IntOpt('reservation_expire',
default=86400,
help='Number of seconds until a reservation expires.'),
@ -1047,6 +1055,10 @@ resources = [
'quota_snapshot_gigabytes'),
ReservableResource('share_networks', '_sync_share_networks',
'quota_share_networks'),
ReservableResource('share_groups', '_sync_share_groups',
'quota_share_groups'),
ReservableResource('share_group_snapshots', '_sync_share_group_snapshots',
'quota_share_group_snapshots'),
]

View File

@ -27,15 +27,15 @@ from manila.common import constants
from manila.db import base
from manila import exception
from manila.i18n import _
from manila import quota
from manila.scheduler import rpcapi as scheduler_rpcapi
from manila import share
from manila.share import rpcapi as share_rpcapi
from manila.share import share_types
CONF = cfg.CONF
LOG = log.getLogger(__name__)
QUOTAS = quota.QUOTAS
class API(base.Base):
@ -138,6 +138,28 @@ class API(base.Base):
"types supported by the share group type.")
raise exception.InvalidInput(reason=msg)
try:
reservations = QUOTAS.reserve(context, share_groups=1)
except exception.OverQuota as e:
overs = e.kwargs['overs']
usages = e.kwargs['usages']
quotas = e.kwargs['quotas']
def _consumed(name):
return (usages[name]['reserved'] + usages[name]['in_use'])
if 'share_groups' in overs:
msg = ("Quota exceeded for '%(s_uid)s' user in '%(s_pid)s' "
"project. (%(d_consumed)d of "
"%(d_quota)d already consumed).")
LOG.warning(msg, {
's_pid': context.project_id,
's_uid': context.user_id,
'd_consumed': _consumed('share_groups'),
'd_quota': quotas['share_groups'],
})
raise exception.ShareGroupsLimitExceeded()
options = {
'share_group_type_id': share_group_type_id,
'source_share_group_snapshot_id': source_share_group_snapshot_id,
@ -154,9 +176,9 @@ class API(base.Base):
if original_share_group:
options['host'] = original_share_group['host']
share_group = self.db.share_group_create(context, options)
share_group = None
try:
share_group = self.db.share_group_create(context, options)
if share_group_snapshot:
members = self.db.share_group_snapshot_members_get_all(
context, source_share_group_snapshot_id)
@ -178,8 +200,16 @@ class API(base.Base):
share_network_id=share_network_id)
except Exception:
with excutils.save_and_reraise_exception():
self.db.share_group_destroy(
context.elevated(), share_group['id'])
if share_group:
self.db.share_group_destroy(
context.elevated(), share_group['id'])
QUOTAS.rollback(context, reservations)
try:
QUOTAS.commit(context, reservations)
except Exception:
with excutils.save_and_reraise_exception():
QUOTAS.rollback(context, reservations)
request_spec = {'share_group_id': share_group['id']}
request_spec.update(options)
@ -224,7 +254,30 @@ class API(base.Base):
share_group = self.db.share_group_update(
context, share_group_id, {'status': constants.STATUS_DELETING})
self.share_rpcapi.delete_share_group(context, share_group)
try:
reservations = QUOTAS.reserve(
context,
share_groups=-1,
project_id=share_group['project_id'],
user_id=share_group['user_id'],
)
except exception.OverQuota as e:
reservations = None
LOG.exception(
("Failed to update quota for deleting share group: %s"), e)
try:
self.share_rpcapi.delete_share_group(context, share_group)
except Exception:
with excutils.save_and_reraise_exception():
QUOTAS.rollback(context, reservations)
if reservations:
QUOTAS.commit(
context, reservations,
project_id=share_group['project_id'],
user_id=share_group['user_id'],
)
def update(self, context, group, fields):
return self.db.share_group_update(context, group['id'], fields)
@ -285,8 +338,31 @@ class API(base.Base):
"status": constants.STATUS_AVAILABLE})
raise exception.InvalidShareGroup(reason=msg)
snap = self.db.share_group_snapshot_create(context, options)
try:
reservations = QUOTAS.reserve(context, share_group_snapshots=1)
except exception.OverQuota as e:
overs = e.kwargs['overs']
usages = e.kwargs['usages']
quotas = e.kwargs['quotas']
def _consumed(name):
return (usages[name]['reserved'] + usages[name]['in_use'])
if 'share_group_snapshots' in overs:
msg = ("Quota exceeded for '%(s_uid)s' user in '%(s_pid)s' "
"project. (%(d_consumed)d of "
"%(d_quota)d already consumed).")
LOG.warning(msg, {
's_pid': context.project_id,
's_uid': context.user_id,
'd_consumed': _consumed('share_group_snapshots'),
'd_quota': quotas['share_group_snapshots'],
})
raise exception.ShareGroupSnapshotsLimitExceeded()
snap = None
try:
snap = self.db.share_group_snapshot_create(context, options)
members = []
for s in shares:
member_options = {
@ -308,7 +384,15 @@ class API(base.Base):
except Exception:
with excutils.save_and_reraise_exception():
# This will delete the snapshot and all of it's members
self.db.share_group_snapshot_destroy(context, snap['id'])
if snap:
self.db.share_group_snapshot_destroy(context, snap['id'])
QUOTAS.rollback(context, reservations)
try:
QUOTAS.commit(context, reservations)
except Exception:
with excutils.save_and_reraise_exception():
QUOTAS.rollback(context, reservations)
return snap
@ -325,10 +409,30 @@ class API(base.Base):
self.db.share_group_snapshot_update(
context, snap_id, {'status': constants.STATUS_DELETING})
try:
reservations = QUOTAS.reserve(
context,
share_group_snapshots=-1,
project_id=snap['project_id'],
user_id=snap['user_id'],
)
except exception.OverQuota as e:
reservations = None
LOG.exception(
("Failed to update quota for deleting share group snapshot: "
"%s"), e)
# Cast to share manager
self.share_rpcapi.delete_share_group_snapshot(
context, snap, share_group['host'])
if reservations:
QUOTAS.commit(
context, reservations,
project_id=snap['project_id'],
user_id=snap['user_id'],
)
def update_share_group_snapshot(self, context, share_group_snapshot,
fields):
return self.db.share_group_snapshot_update(

View File

@ -18,8 +18,6 @@
Tests for manila.api.v2.quota_sets.py
"""
import copy
import ddt
import mock
from oslo_config import cfg
@ -37,18 +35,18 @@ from manila import utils
CONF = cfg.CONF
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'
REQ.environ['manila.context'].project_id = 'foo_project_id'
REQ_WITH_USER = copy.deepcopy(REQ)
REQ_WITH_USER.environ['manila.context'].user_id = 'foo_user_id'
REQ_WITH_USER.environ['QUERY_STRING'] = 'user_id=foo_user_id'
REQ_MEMBER = copy.deepcopy(REQ)
REQ_MEMBER.environ['manila.context'].is_admin = False
def _get_request(is_admin, user_in_url):
req = mock.MagicMock(
api_version_request=api_version.APIVersionRequest("2.40"))
req.environ = {'manila.context': context.get_admin_context()}
req.environ['manila.context'].is_admin = is_admin
req.environ['manila.context'].auth_token = 'foo_auth_token'
req.environ['manila.context'].project_id = 'foo_project_id'
if user_in_url:
req.environ['manila.context'].user_id = 'foo_user_id'
req.environ['QUERY_STRING'] = 'user_id=foo_user_id'
return req
@ddt.ddt
@ -72,8 +70,11 @@ class QuotaSetsControllerTest(test.TestCase):
{"gigabytes": 7},
{"snapshot_gigabytes": 10001},
{"share_networks": 12345},
{"share_groups": 123456},
{"share_group_snapshots": 123456},
)
def test_defaults(self, quotas):
req = _get_request(True, False)
for k, v in quotas.items():
CONF.set_default('quota_' + k, v)
expected = {
@ -84,14 +85,17 @@ class QuotaSetsControllerTest(test.TestCase):
'snapshots': quotas.get('snapshots', 50),
'snapshot_gigabytes': quotas.get('snapshot_gigabytes', 1000),
'share_networks': quotas.get('share_networks', 10),
'share_groups': quotas.get('share_groups', 50),
'share_group_snapshots': quotas.get(
'share_group_snapshots', 50),
}
}
result = self.controller.defaults(REQ, self.project_id)
result = self.controller.defaults(req, self.project_id)
self.assertEqual(expected, result)
self.mock_policy_check.assert_called_once_with(
REQ.environ['manila.context'], self.resource_name, 'show')
req.environ['manila.context'], self.resource_name, 'show')
@ddt.data(
('os-', '1.0', quota_sets.QuotaSetsControllerLegacy, 'defaults'),
@ -124,17 +128,18 @@ class QuotaSetsControllerTest(test.TestCase):
@staticmethod
def _get_share_type_request_object(microversion=None):
req = copy.deepcopy(REQ)
req = _get_request(True, False)
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):
@ddt.data('2.39', '2.40')
def test_share_type_quota_detail(self, microversion):
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')
req = self._get_share_type_request_object(microversion)
quotas = {
"shares": 23,
"snapshots": 34,
@ -176,11 +181,12 @@ class QuotaSetsControllerTest(test.TestCase):
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):
@ddt.data('2.39', '2.40')
def test_show_share_type_quota(self, microversion):
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')
req = self._get_share_type_request_object(microversion)
quotas = {
"shares": 23,
"snapshots": 34,
@ -262,7 +268,30 @@ class QuotaSetsControllerTest(test.TestCase):
self.assertIsNone(result)
@ddt.data(REQ, REQ_WITH_USER)
@ddt.data(
{},
{"quota_set": {}},
{"quota_set": {"foo": "bar"}},
{"foo": "bar"},
)
def test__ensure_share_group_related_args_are_absent_success(self, body):
result = self.controller._ensure_share_group_related_args_are_absent(
body)
self.assertIsNone(result)
@ddt.data(
{"share_groups": 5},
{"share_group_snapshots": 6},
{"quota_set": {"share_groups": 7}},
{"quota_set": {"share_group_snapshots": 8}},
)
def test__ensure_share_group_related_args_are_absent_error(self, body):
self.assertRaises(
webob.exc.HTTPBadRequest,
self.controller._ensure_share_group_related_args_are_absent, body)
@ddt.data(_get_request(True, True), _get_request(True, False))
def test__ensure_share_type_arg_is_absent(self, req):
result = self.controller._ensure_share_type_arg_is_absent(req)
@ -276,7 +305,7 @@ class QuotaSetsControllerTest(test.TestCase):
self.controller._ensure_share_type_arg_is_absent,
req)
@ddt.data(REQ, REQ_WITH_USER)
@ddt.data(_get_request(True, True), _get_request(True, False))
def test_quota_detail(self, request):
request.api_version_request = api_version.APIVersionRequest('2.25')
quotas = {
@ -318,7 +347,7 @@ class QuotaSetsControllerTest(test.TestCase):
self.mock_policy_check.assert_called_once_with(
request.environ['manila.context'], self.resource_name, 'show')
@ddt.data(REQ, REQ_WITH_USER)
@ddt.data(_get_request(True, True), _get_request(True, False))
def test_show_quota(self, request):
quotas = {
"shares": 23,
@ -326,6 +355,8 @@ class QuotaSetsControllerTest(test.TestCase):
"gigabytes": 45,
"snapshot_gigabytes": 56,
"share_networks": 67,
"share_groups": 53,
"share_group_snapshots": 57,
}
expected = {
'quota_set': {
@ -335,6 +366,9 @@ class QuotaSetsControllerTest(test.TestCase):
'snapshots': quotas.get('snapshots', 50),
'snapshot_gigabytes': quotas.get('snapshot_gigabytes', 1000),
'share_networks': quotas.get('share_networks', 10),
'share_groups': quotas.get('share_groups', 50),
'share_group_snapshots': quotas.get(
'share_group_snapshots', 50),
}
}
for k, v in quotas.items():
@ -347,6 +381,7 @@ class QuotaSetsControllerTest(test.TestCase):
request.environ['manila.context'], self.resource_name, 'show')
def test_show_quota_not_authorized(self):
req = _get_request(True, False)
self.mock_object(
quota_sets.db,
'authorize_project_context',
@ -355,11 +390,11 @@ class QuotaSetsControllerTest(test.TestCase):
self.assertRaises(
webob.exc.HTTPForbidden,
self.controller.show,
REQ, self.project_id)
req, self.project_id)
self.mock_policy_check.assert_called_once_with(
REQ.environ['manila.context'], self.resource_name, 'show')
req.environ['manila.context'], self.resource_name, 'show')
@ddt.data(REQ, REQ_WITH_USER)
@ddt.data(_get_request(True, True), _get_request(True, False))
def test_update_quota(self, request):
self.mock_object(
quota_sets.db, 'share_type_get_by_name_or_id',
@ -374,6 +409,8 @@ class QuotaSetsControllerTest(test.TestCase):
'snapshots': 50,
'snapshot_gigabytes': 1000,
'share_networks': 10,
'share_groups': 50,
'share_group_snapshots': 50,
}
}
mock_policy_update_check_call = mock.call(
@ -394,12 +431,13 @@ class QuotaSetsControllerTest(test.TestCase):
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):
@ddt.data('2.39', '2.40')
def test_update_share_type_quota(self, microversion):
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')
req = self._get_share_type_request_object(microversion)
CONF.set_default('quota_shares', 789)
body = {'quota_set': {'tenant_id': self.project_id, 'shares': 788}}
@ -468,14 +506,15 @@ class QuotaSetsControllerTest(test.TestCase):
@ddt.data(-2, 'foo', {1: 2}, [1])
def test_update_quota_with_invalid_value(self, value):
req = _get_request(True, False)
body = {'quota_set': {'tenant_id': self.project_id, 'shares': value}}
self.assertRaises(
webob.exc.HTTPBadRequest,
self.controller.update,
REQ, self.project_id, body=body)
req, self.project_id, body=body)
self.mock_policy_check.assert_called_once_with(
REQ.environ['manila.context'], self.resource_name, 'update')
req.environ['manila.context'], self.resource_name, 'update')
def test_user_quota_can_not_be_bigger_than_tenant_quota(self):
value = 777
@ -486,14 +525,14 @@ class QuotaSetsControllerTest(test.TestCase):
'shares': value + 1,
}
}
req = _get_request(True, True)
self.assertRaises(
webob.exc.HTTPBadRequest,
self.controller.update,
REQ_WITH_USER, self.project_id, body=body)
req, self.project_id, body=body)
self.mock_policy_check.assert_called_once_with(
REQ_WITH_USER.environ['manila.context'], self.resource_name,
'update')
req.environ['manila.context'], self.resource_name, 'update')
def test_update_inexistent_quota(self):
body = {
@ -502,23 +541,25 @@ class QuotaSetsControllerTest(test.TestCase):
'fake_quota': 13,
}
}
req = _get_request(True, False)
self.assertRaises(
webob.exc.HTTPBadRequest,
self.controller.update,
REQ, self.project_id, body=body)
req, self.project_id, body=body)
self.mock_policy_check.assert_called_once_with(
REQ.environ['manila.context'], self.resource_name, 'update')
req.environ['manila.context'], self.resource_name, 'update')
def test_update_quota_not_authorized(self):
body = {'quota_set': {'tenant_id': self.project_id, 'shares': 13}}
req = _get_request(False, False)
self.assertRaises(
webob.exc.HTTPForbidden,
self.controller.update,
REQ_MEMBER, self.project_id, body=body)
req, self.project_id, body=body)
self.mock_policy_check.assert_called_once_with(
REQ_MEMBER.environ['manila.context'], self.resource_name, 'update')
req.environ['manila.context'], self.resource_name, 'update')
@ddt.data(
('os-quota-sets', '1.0', quota_sets.QuotaSetsControllerLegacy),
@ -597,8 +638,9 @@ class QuotaSetsControllerTest(test.TestCase):
project_id = 'foo_project_id'
self.mock_object(quota_sets.QUOTAS, 'destroy_all_by_project_and_user')
self.mock_object(quota_sets.QUOTAS, 'destroy_all_by_project')
req = _get_request(True, True)
result = self.controller.delete(REQ_WITH_USER, project_id)
result = self.controller.delete(req, project_id)
self.assertTrue(
utils.IsAMatcher(webob.response.Response) == result
@ -607,13 +649,12 @@ class QuotaSetsControllerTest(test.TestCase):
self.assertEqual(202, result.status_code)
(quota_sets.QUOTAS.destroy_all_by_project_and_user.
assert_called_once_with(
REQ_WITH_USER.environ['manila.context'],
req.environ['manila.context'],
project_id,
REQ_WITH_USER.environ['manila.context'].user_id))
req.environ['manila.context'].user_id))
self.assertFalse(quota_sets.QUOTAS.destroy_all_by_project.called)
self.mock_policy_check.assert_called_once_with(
REQ_WITH_USER.environ['manila.context'], self.resource_name,
'delete')
req.environ['manila.context'], self.resource_name, 'delete')
def test_delete_share_type_quota(self):
req = self._get_share_type_request_object('2.39')
@ -656,12 +697,13 @@ class QuotaSetsControllerTest(test.TestCase):
quota_sets.db.share_type_get_by_name_or_id.assert_not_called()
def test_delete_not_authorized(self):
req = _get_request(False, False)
self.assertRaises(
webob.exc.HTTPForbidden,
self.controller.delete,
REQ_MEMBER, self.project_id)
req, self.project_id)
self.mock_policy_check.assert_called_once_with(
REQ_MEMBER.environ['manila.context'], self.resource_name, 'delete')
req.environ['manila.context'], self.resource_name, 'delete')
@ddt.data(
('os-quota-sets', '2.7', quota_sets.QuotaSetsControllerLegacy),

View File

@ -0,0 +1,70 @@
# Copyright (c) 2017 Mirantis, Inc.
# All Rights Reserved.
#
# 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.
import ddt
from manila.api.openstack import api_version_request as api_version
from manila.api.views import quota_class_sets
from manila import test
from manila.tests.api import fakes
@ddt.ddt
class ViewBuilderTestCase(test.TestCase):
def setUp(self):
super(ViewBuilderTestCase, self).setUp()
self.builder = quota_class_sets.ViewBuilder()
def test__collection_name(self):
self.assertEqual('quota_class_set', self.builder._collection_name)
@ddt.data(
("fake_quota_class", "2.40"), (None, "2.40"),
("fake_quota_class", "2.39"), (None, "2.39"),
)
@ddt.unpack
def test_detail_list_with_share_type(self, quota_class, microversion):
req = fakes.HTTPRequest.blank('/quota-sets', version=microversion)
quota_class_set = {
"shares": 13,
"gigabytes": 31,
"snapshots": 14,
"snapshot_gigabytes": 41,
"share_groups": 15,
"share_group_snapshots": 51,
"share_networks": 16,
}
expected = {self.builder._collection_name: {
"shares": quota_class_set["shares"],
"gigabytes": quota_class_set["gigabytes"],
"snapshots": quota_class_set["snapshots"],
"snapshot_gigabytes": quota_class_set["snapshot_gigabytes"],
"share_networks": quota_class_set["share_networks"],
}}
if quota_class:
expected[self.builder._collection_name]['id'] = quota_class
if (api_version.APIVersionRequest(microversion) >= (
api_version.APIVersionRequest("2.40"))):
expected[self.builder._collection_name][
"share_groups"] = quota_class_set["share_groups"]
expected[self.builder._collection_name][
"share_group_snapshots"] = quota_class_set[
"share_group_snapshots"]
result = self.builder.detail_list(
req, quota_class_set, quota_class=quota_class)
self.assertEqual(expected, result)

View File

@ -0,0 +1,79 @@
# Copyright (c) 2017 Mirantis, Inc.
# All Rights Reserved.
#
# 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.
import ddt
from manila.api.openstack import api_version_request as api_version
from manila.api.views import quota_sets
from manila import test
from manila.tests.api import fakes
@ddt.ddt
class ViewBuilderTestCase(test.TestCase):
def setUp(self):
super(ViewBuilderTestCase, self).setUp()
self.builder = quota_sets.ViewBuilder()
def test__collection_name(self):
self.assertEqual('quota_set', self.builder._collection_name)
@ddt.data(
('fake_project_id', 'fake_share_type_id', "2.40"),
(None, 'fake_share_type_id', "2.40"),
('fake_project_id', None, "2.40"),
(None, None, "2.40"),
('fake_project_id', 'fake_share_type_id', "2.39"),
(None, 'fake_share_type_id', "2.39"),
('fake_project_id', None, "2.39"),
(None, None, "2.39"),
)
@ddt.unpack
def test_detail_list_with_share_type(self, project_id, share_type,
microversion):
req = fakes.HTTPRequest.blank('/quota-sets', version=microversion)
quota_set = {
"shares": 13,
"gigabytes": 31,
"snapshots": 14,
"snapshot_gigabytes": 41,
"share_groups": 15,
"share_group_snapshots": 51,
"share_networks": 16,
}
expected = {self.builder._collection_name: {
"shares": quota_set["shares"],
"gigabytes": quota_set["gigabytes"],
"snapshots": quota_set["snapshots"],
"snapshot_gigabytes": quota_set["snapshot_gigabytes"],
}}
if project_id:
expected[self.builder._collection_name]['id'] = project_id
if not share_type:
expected[self.builder._collection_name][
"share_networks"] = quota_set["share_networks"]
if (api_version.APIVersionRequest(microversion) >= (
api_version.APIVersionRequest("2.40"))):
expected[self.builder._collection_name][
"share_groups"] = quota_set["share_groups"]
expected[self.builder._collection_name][
"share_group_snapshots"] = quota_set[
"share_group_snapshots"]
result = self.builder.detail_list(
req, quota_set, project_id=project_id, share_type=share_type)
self.assertEqual(expected, result)

View File

@ -79,7 +79,10 @@ def fake_share_group_snapshot(id, **kwargs):
class ShareGroupsAPITestCase(test.TestCase):
def setUp(self):
super(ShareGroupsAPITestCase, self).setUp()
self.context = context.get_admin_context()
self.user_id = 'fake_user_id'
self.project_id = 'fake_project_id'
self.context = context.RequestContext(
user_id=self.user_id, project_id=self.project_id, is_admin=True)
self.scheduler_rpcapi = mock.Mock()
self.share_rpcapi = mock.Mock()
self.share_api = mock.Mock()
@ -108,8 +111,12 @@ class ShareGroupsAPITestCase(test.TestCase):
{'share_type_id': self.fake_share_type_2['id']},
]
}
self.mock_object(db_driver, 'share_group_type_get',
mock.Mock(return_value=self.fake_share_group_type))
self.mock_object(
db_driver, 'share_group_type_get',
mock.Mock(return_value=self.fake_share_group_type))
self.mock_object(share_group_api.QUOTAS, 'reserve')
self.mock_object(share_group_api.QUOTAS, 'commit')
self.mock_object(share_group_api.QUOTAS, 'rollback')
def test_create_empty_request(self):
share_group = fake_share_group(
@ -126,6 +133,11 @@ class ShareGroupsAPITestCase(test.TestCase):
db_driver.share_group_create.assert_called_once_with(
self.context, expected_values)
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_groups=1)
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_request_spec(self):
"""Ensure the correct values are sent to the scheduler."""
@ -150,6 +162,11 @@ class ShareGroupsAPITestCase(test.TestCase):
self.scheduler_rpcapi.create_share_group.assert_called_once_with(
self.context, share_group_id=share_group['id'],
request_spec=expected_request_spec, filter_properties={})
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_groups=1)
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_with_name(self):
fake_name = 'fake_name'
@ -172,6 +189,11 @@ class ShareGroupsAPITestCase(test.TestCase):
self.scheduler_rpcapi.create_share_group.assert_called_once_with(
self.context, share_group_id=share_group['id'],
request_spec=mock.ANY, filter_properties={})
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_groups=1)
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_with_description(self):
fake_desc = 'fake_desc'
@ -190,6 +212,11 @@ class ShareGroupsAPITestCase(test.TestCase):
db_driver.share_group_create.assert_called_once_with(
self.context, expected_values)
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_groups=1)
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_with_multiple_share_types(self):
fake_share_types = [self.fake_share_type, self.fake_share_type_2]
@ -213,6 +240,11 @@ class ShareGroupsAPITestCase(test.TestCase):
db_driver.share_group_create.assert_called_once_with(
self.context, expected_values)
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_groups=1)
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_with_share_type_not_found(self):
self.mock_object(share_types, 'get_share_type',
@ -234,6 +266,31 @@ class ShareGroupsAPITestCase(test.TestCase):
self.api.create,
self.context, share_type_ids=[self.fake_share_type['id']])
share_group_api.QUOTAS.reserve.assert_not_called()
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_with_error_on_quota_reserve(self):
overs = ["share_groups"]
usages = {"share_groups": {"reserved": 1, "in_use": 3, "limit": 4}}
quotas = {"share_groups": 5}
share_group_api.QUOTAS.reserve.side_effect = exception.OverQuota(
overs=overs,
usages=usages,
quotas=quotas,
)
self.mock_object(share_group_api.LOG, "warning")
self.assertRaises(
exception.ShareGroupsLimitExceeded,
self.api.create, self.context)
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_groups=1)
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
share_group_api.LOG.warning.assert_called_once_with(mock.ANY, mock.ANY)
def test_create_driver_handles_share_servers_is_false_with_net_id(self):
fake_share_types = [self.fake_share_type]
self.mock_object(share_types, 'get_share_type')
@ -266,6 +323,10 @@ class ShareGroupsAPITestCase(test.TestCase):
self.api.create,
self.context, share_type_ids=fake_share_type_ids)
share_group_api.QUOTAS.reserve.assert_not_called()
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_with_conflicting_share_type_and_share_network(self):
fake_share_type = {
'name': 'default',
@ -283,6 +344,10 @@ class ShareGroupsAPITestCase(test.TestCase):
self.context, share_type_ids=fake_share_types,
share_network_id="fake_sn")
share_group_api.QUOTAS.reserve.assert_not_called()
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_with_source_share_group_snapshot_id(self):
snap = fake_share_group_snapshot(
"fake_source_share_group_snapshot_id",
@ -341,6 +406,11 @@ class ShareGroupsAPITestCase(test.TestCase):
self.context, expected_values)
self.share_rpcapi.create_share_group.assert_called_once_with(
self.context, share_group, orig_share_group['host'])
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_groups=1)
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_with_source_share_group_snapshot_id_with_member(self):
snap = fake_share_group_snapshot(
@ -403,6 +473,11 @@ class ShareGroupsAPITestCase(test.TestCase):
self.assertTrue(self.share_api.create.called)
self.share_rpcapi.create_share_group.assert_called_once_with(
self.context, share_group, orig_share_group['host'])
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_groups=1)
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_with_source_sg_snapshot_id_with_members_error(self):
snap = fake_share_group_snapshot(
@ -465,6 +540,12 @@ class ShareGroupsAPITestCase(test.TestCase):
self.assertEqual(2, self.share_api.create.call_count)
self.assertEqual(1, db_driver.share_group_destroy.call_count)
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_groups=1)
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
def test_create_with_source_sg_snapshot_id_error_snapshot_status(self):
snap = fake_share_group_snapshot(
"fake_source_share_group_snapshot_id",
@ -478,6 +559,10 @@ class ShareGroupsAPITestCase(test.TestCase):
self.api.create,
self.context, source_share_group_snapshot_id=snap['id'])
share_group_api.QUOTAS.reserve.assert_not_called()
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_with_source_sg_snapshot_id_snap_not_found(self):
snap = fake_share_group_snapshot(
"fake_source_share_group_snapshot_id",
@ -492,6 +577,10 @@ class ShareGroupsAPITestCase(test.TestCase):
self.api.create,
self.context, source_share_group_snapshot_id=snap['id'])
share_group_api.QUOTAS.reserve.assert_not_called()
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_with_multiple_fields(self):
fake_desc = 'fake_desc'
fake_name = 'fake_name'
@ -512,6 +601,11 @@ class ShareGroupsAPITestCase(test.TestCase):
db_driver.share_group_create.assert_called_once_with(
self.context, expected_values)
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_groups=1)
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_with_error_on_creation(self):
share_group = fake_share_group(
@ -528,11 +622,16 @@ class ShareGroupsAPITestCase(test.TestCase):
db_driver.share_group_create.assert_called_once_with(
self.context, expected_values)
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_groups=1)
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
def test_delete_creating_no_host(self):
share_group = fake_share_group(
'fakeid', user_id=self.context.user_id,
project_id=self.context.project_id,
'fakeid', user_id=self.user_id + '_different_user',
project_id=self.project_id + '_in_different_project',
status=constants.STATUS_CREATING)
self.mock_object(db_driver, 'share_group_destroy')
@ -540,6 +639,9 @@ class ShareGroupsAPITestCase(test.TestCase):
db_driver.share_group_destroy.assert_called_once_with(
mock.ANY, share_group['id'])
share_group_api.QUOTAS.reserve.assert_not_called()
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
def test_delete_creating_with_host(self):
share_group = fake_share_group(
@ -553,8 +655,8 @@ class ShareGroupsAPITestCase(test.TestCase):
def test_delete_available(self):
share_group = fake_share_group(
'fakeid', user_id=self.context.user_id,
project_id=self.context.project_id,
'fakeid', user_id=self.user_id + '_different_user',
project_id=self.project_id + '_in_different_project',
status=constants.STATUS_AVAILABLE, host="fake_host")
deleted_share_group = copy.deepcopy(share_group)
deleted_share_group['status'] = constants.STATUS_DELETING
@ -570,6 +672,16 @@ class ShareGroupsAPITestCase(test.TestCase):
{'status': constants.STATUS_DELETING})
self.share_rpcapi.delete_share_group.assert_called_once_with(
self.context, deleted_share_group)
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_groups=-1,
project_id=share_group['project_id'],
user_id=share_group['user_id'])
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context,
share_group_api.QUOTAS.reserve.return_value,
project_id=share_group['project_id'],
user_id=share_group['user_id'])
share_group_api.QUOTAS.rollback.assert_not_called()
def test_delete_error_with_host(self):
share_group = fake_share_group(
@ -591,6 +703,16 @@ class ShareGroupsAPITestCase(test.TestCase):
{'status': constants.STATUS_DELETING})
self.api.share_rpcapi.delete_share_group.assert_called_once_with(
self.context, deleted_share_group)
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_groups=-1,
project_id=share_group['project_id'],
user_id=share_group['user_id'])
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context,
share_group_api.QUOTAS.reserve.return_value,
project_id=share_group['project_id'],
user_id=share_group['user_id'])
share_group_api.QUOTAS.rollback.assert_not_called()
def test_delete_error_without_host(self):
share_group = fake_share_group(
@ -603,6 +725,9 @@ class ShareGroupsAPITestCase(test.TestCase):
db_driver.share_group_destroy.assert_called_once_with(
mock.ANY, share_group['id'])
share_group_api.QUOTAS.reserve.assert_not_called()
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
def test_delete_with_shares(self):
share_group = fake_share_group(
@ -617,6 +742,10 @@ class ShareGroupsAPITestCase(test.TestCase):
exception.InvalidShareGroup,
self.api.delete, self.context, share_group)
share_group_api.QUOTAS.reserve.assert_not_called()
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
def test_delete_with_share_group_snapshots(self):
share_group = fake_share_group(
'fakeid', user_id=self.context.user_id,
@ -630,6 +759,10 @@ class ShareGroupsAPITestCase(test.TestCase):
exception.InvalidShareGroup,
self.api.delete, self.context, share_group)
share_group_api.QUOTAS.reserve.assert_not_called()
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
@ddt.data({}, {"name": "fake_name"}, {"description": "fake_description"})
def test_update(self, expected_values):
share_group = fake_share_group(
@ -739,6 +872,11 @@ class ShareGroupsAPITestCase(test.TestCase):
self.context, expected_values)
self.share_rpcapi.create_share_group_snapshot.assert_called_once_with(
self.context, snap, share_group['host'])
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_group_snapshots=1)
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_sg_snapshot_minimal_request_no_members_with_name(self):
fake_name = 'fake_name'
@ -772,6 +910,11 @@ class ShareGroupsAPITestCase(test.TestCase):
self.context, expected_values)
self.share_rpcapi.create_share_group_snapshot.assert_called_once_with(
self.context, snap, share_group['host'])
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_group_snapshots=1)
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_group_snapshot_minimal_request_no_members_with_desc(self):
fake_description = 'fake_description'
@ -807,6 +950,11 @@ class ShareGroupsAPITestCase(test.TestCase):
self.context, expected_values)
self.share_rpcapi.create_share_group_snapshot.assert_called_once_with(
self.context, snap, share_group['host'])
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_group_snapshots=1)
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_share_group_snapshot_group_does_not_exist(self):
share_group = fake_share_group(
@ -837,6 +985,46 @@ class ShareGroupsAPITestCase(test.TestCase):
db_driver.share_group_get.assert_called_once_with(
self.context, share_group['id'])
share_group_api.QUOTAS.reserve.assert_not_called()
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_share_group_snapshot_failure_reserving_quota(self):
overs = ["share_group_snapshots"]
usages = {"share_group_snapshots": {
"reserved": 1,
"in_use": 3,
"limit": 4,
}}
quotas = {"share_group_snapshots": 5}
share_group = fake_share_group(
"fake_group_id", user_id=self.context.user_id,
project_id=self.context.project_id,
status=constants.STATUS_AVAILABLE)
self.mock_object(
db_driver, "share_group_get", mock.Mock(return_value=share_group))
self.mock_object(
db_driver, "share_get_all_by_share_group_id",
mock.Mock(return_value=[]))
share_group_api.QUOTAS.reserve.side_effect = exception.OverQuota(
overs=overs,
usages=usages,
quotas=quotas,
)
self.mock_object(share_group_api.LOG, "warning")
self.assertRaises(
exception.ShareGroupSnapshotsLimitExceeded,
self.api.create_share_group_snapshot,
self.context, share_group_id=share_group["id"])
db_driver.share_group_get.assert_called_once_with(
self.context, share_group["id"])
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_group_snapshots=1)
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
share_group_api.LOG.warning.assert_called_once_with(mock.ANY, mock.ANY)
def test_create_share_group_snapshot_group_in_creating(self):
self.mock_object(
@ -851,6 +1039,9 @@ class ShareGroupsAPITestCase(test.TestCase):
db_driver.share_group_get.assert_called_once_with(
self.context, "fake_id")
share_group_api.QUOTAS.reserve.assert_not_called()
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_share_group_snapshot_with_member(self):
share_group = fake_share_group(
@ -898,6 +1089,11 @@ class ShareGroupsAPITestCase(test.TestCase):
self.context, expected_member_values)
self.share_rpcapi.create_share_group_snapshot.assert_called_once_with(
self.context, snap, share_group['host'])
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_group_snapshots=1)
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_share_group_snapshot_with_member_share_in_creating(self):
share_group = fake_share_group(
@ -919,6 +1115,9 @@ class ShareGroupsAPITestCase(test.TestCase):
db_driver.share_group_get.assert_called_once_with(
self.context, share_group['id'])
share_group_api.QUOTAS.reserve.assert_not_called()
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_share_group_snapshot_with_two_members(self):
share_group = fake_share_group(
@ -979,6 +1178,11 @@ class ShareGroupsAPITestCase(test.TestCase):
self.context, expected_member_2_values)
self.share_rpcapi.create_share_group_snapshot.assert_called_once_with(
self.context, snap, share_group['host'])
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_group_snapshots=1)
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
share_group_api.QUOTAS.rollback.assert_not_called()
def test_create_share_group_snapshot_error_creating_member(self):
share_group = fake_share_group(
@ -1031,6 +1235,11 @@ class ShareGroupsAPITestCase(test.TestCase):
self.context, expected_member_values)
db_driver.share_group_snapshot_destroy.assert_called_once_with(
self.context, snap['id'])
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_group_snapshots=1)
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value)
def test_delete_share_group_snapshot(self):
share_group = fake_share_group('fake_id', host="fake_host")
@ -1049,6 +1258,44 @@ class ShareGroupsAPITestCase(test.TestCase):
self.context, sg_snap['id'], {'status': constants.STATUS_DELETING})
self.share_rpcapi.delete_share_group_snapshot.assert_called_once_with(
self.context, sg_snap, share_group['host'])
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_group_snapshots=-1,
project_id=share_group['project_id'],
user_id=share_group['user_id'])
share_group_api.QUOTAS.commit.assert_called_once_with(
self.context, share_group_api.QUOTAS.reserve.return_value,
project_id=share_group['project_id'],
user_id=share_group['user_id'])
share_group_api.QUOTAS.rollback.assert_not_called()
def test_delete_share_group_snapshot_fail_on_quota_reserve(self):
share_group = fake_share_group('fake_id', host="fake_host")
sg_snap = fake_share_group_snapshot(
'fake_groupsnap_id', share_group_id='fake_id',
status=constants.STATUS_AVAILABLE)
self.mock_object(db_driver, 'share_group_get',
mock.Mock(return_value=share_group))
self.mock_object(db_driver, 'share_group_snapshot_update')
share_group_api.QUOTAS.reserve.side_effect = exception.OverQuota(
'Failure')
self.mock_object(share_group_api.LOG, 'exception')
self.api.delete_share_group_snapshot(self.context, sg_snap)
db_driver.share_group_get.assert_called_once_with(
self.context, "fake_id")
db_driver.share_group_snapshot_update.assert_called_once_with(
self.context, sg_snap['id'], {'status': constants.STATUS_DELETING})
self.share_rpcapi.delete_share_group_snapshot.assert_called_once_with(
self.context, sg_snap, share_group['host'])
share_group_api.QUOTAS.reserve.assert_called_once_with(
self.context, share_group_snapshots=-1,
project_id=share_group['project_id'],
user_id=share_group['user_id'])
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
share_group_api.LOG.exception.assert_called_once_with(
mock.ANY, mock.ANY)
def test_delete_share_group_snapshot_group_does_not_exist(self):
snap = fake_share_group_snapshot(
@ -1064,6 +1311,9 @@ class ShareGroupsAPITestCase(test.TestCase):
db_driver.share_group_get.assert_called_once_with(
self.context, "fake_id")
share_group_api.QUOTAS.reserve.assert_not_called()
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
def test_delete_share_group_snapshot_creating_status(self):
snap = fake_share_group_snapshot(
@ -1077,6 +1327,9 @@ class ShareGroupsAPITestCase(test.TestCase):
db_driver.share_group_get.assert_called_once_with(
self.context, snap['share_group_id'])
share_group_api.QUOTAS.reserve.assert_not_called()
share_group_api.QUOTAS.commit.assert_not_called()
share_group_api.QUOTAS.rollback.assert_not_called()
@ddt.data({}, {"name": "fake_name"})
def test_update_share_group_snapshot_no_values(self, expected_values):

View File

@ -711,6 +711,6 @@ class QuotaEngineTestCase(test.TestCase):
def test_current_common_resources(self):
self.assertEqual(
['gigabytes', 'share_networks', 'shares',
'snapshot_gigabytes', 'snapshots'],
['gigabytes', 'share_group_snapshots', 'share_groups',
'share_networks', 'shares', 'snapshot_gigabytes', 'snapshots'],
quota.QUOTAS.resources)

View File

@ -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.39",
default="2.40",
help="The maximum api microversion is configured to be the "
"value of the latest microversion supported by Manila."),
cfg.StrOpt("region",

View File

@ -909,7 +909,9 @@ class SharesV2Client(shares_client.SharesClient):
def update_quotas(self, tenant_id, user_id=None, shares=None,
snapshots=None, gigabytes=None, snapshot_gigabytes=None,
share_networks=None, force=True, share_type=None,
share_networks=None,
share_groups=None, share_group_snapshots=None,
force=True, share_type=None,
url=None, version=LATEST_MICROVERSION):
if url is None:
url = self._get_quotas_url(version)
@ -929,6 +931,10 @@ class SharesV2Client(shares_client.SharesClient):
put_body["snapshot_gigabytes"] = snapshot_gigabytes
if share_networks is not None:
put_body["share_networks"] = share_networks
if share_groups is not None:
put_body["share_groups"] = share_groups
if share_group_snapshots is not None:
put_body["share_group_snapshots"] = share_group_snapshots
put_body = json.dumps({"quota_set": put_body})
resp, body = self.put(url, put_body, version=version)

View File

@ -17,11 +17,15 @@ import ddt
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions as lib_exc
import testtools
from testtools import testcase as tc
from manila_tempest_tests.tests.api import base
from manila_tempest_tests import utils
CONF = config.CONF
PRE_SHARE_GROUPS_MICROVERSION = "2.39"
SHARE_GROUPS_MICROVERSION = "2.40"
@ddt.ddt
@ -44,6 +48,9 @@ class SharesAdminQuotasTest(base.BaseSharesAdminTest):
self.assertGreater(int(quotas["shares"]), -2)
self.assertGreater(int(quotas["snapshots"]), -2)
self.assertGreater(int(quotas["share_networks"]), -2)
if utils.is_microversion_supported(SHARE_GROUPS_MICROVERSION):
self.assertGreater(int(quotas["share_groups"]), -2)
self.assertGreater(int(quotas["share_group_snapshots"]), -2)
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
def test_show_quotas(self):
@ -53,6 +60,9 @@ class SharesAdminQuotasTest(base.BaseSharesAdminTest):
self.assertGreater(int(quotas["shares"]), -2)
self.assertGreater(int(quotas["snapshots"]), -2)
self.assertGreater(int(quotas["share_networks"]), -2)
if utils.is_microversion_supported(SHARE_GROUPS_MICROVERSION):
self.assertGreater(int(quotas["share_groups"]), -2)
self.assertGreater(int(quotas["share_group_snapshots"]), -2)
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
def test_show_quotas_for_user(self):
@ -63,6 +73,28 @@ class SharesAdminQuotasTest(base.BaseSharesAdminTest):
self.assertGreater(int(quotas["shares"]), -2)
self.assertGreater(int(quotas["snapshots"]), -2)
self.assertGreater(int(quotas["share_networks"]), -2)
if utils.is_microversion_supported(SHARE_GROUPS_MICROVERSION):
self.assertGreater(int(quotas["share_groups"]), -2)
self.assertGreater(int(quotas["share_group_snapshots"]), -2)
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
@base.skip_if_microversion_not_supported(PRE_SHARE_GROUPS_MICROVERSION)
def test_show_sg_quotas_using_too_old_microversion(self):
quotas = self.shares_v2_client.show_quotas(
self.tenant_id, version=PRE_SHARE_GROUPS_MICROVERSION)
for key in ('share_groups', 'share_group_snapshots'):
self.assertNotIn(key, quotas)
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
@base.skip_if_microversion_not_supported(PRE_SHARE_GROUPS_MICROVERSION)
def test_show_sg_quotas_for_user_using_too_old_microversion(self):
quotas = self.shares_v2_client.show_quotas(
self.tenant_id, self.user_id,
version=PRE_SHARE_GROUPS_MICROVERSION)
for key in ('share_groups', 'share_group_snapshots'):
self.assertNotIn(key, quotas)
@ddt.data(
('id', True),
@ -93,12 +125,16 @@ class SharesAdminQuotasTest(base.BaseSharesAdminTest):
for key in ('shares', 'gigabytes', 'snapshots', 'snapshot_gigabytes'):
self.assertEqual(st_quotas[key], p_quotas[key])
# Verify that we do not have share groups related quotas
# for share types.
for key in ('share_groups', 'share_group_snapshots'):
self.assertNotIn(key, st_quotas)
@ddt.ddt
class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
force_tenant_isolation = True
client_version = '2'
@classmethod
def resource_setup(cls):
@ -109,8 +145,7 @@ class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
def setUp(self):
super(self.__class__, self).setUp()
self.client = self.get_client_with_isolated_creds(
client_version=self.client_version)
self.client = self.get_client_with_isolated_creds(client_version='2')
self.tenant_id = self.client.tenant_id
self.user_id = self.client.user_id
@ -124,6 +159,24 @@ class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
updated = self.client.update_quotas(self.tenant_id, shares=new_quota)
self.assertEqual(new_quota, int(updated["shares"]))
@ddt.data(
"share_groups",
"share_group_snapshots",
)
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
@testtools.skipUnless(
CONF.share.run_share_group_tests, 'Share Group tests disabled.')
@utils.skip_if_microversion_not_supported(SHARE_GROUPS_MICROVERSION)
def test_update_tenant_quota_share_groups(self, quota_key):
# Get current quotas
quotas = self.client.show_quotas(self.tenant_id)
new_quota = int(quotas[quota_key]) + 2
# Set new quota
updated = self.client.update_quotas(
self.tenant_id, **{quota_key: new_quota})
self.assertEqual(new_quota, int(updated[quota_key]))
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
def test_update_user_quota_shares(self):
# get current quotas
@ -135,6 +188,24 @@ class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
self.tenant_id, self.user_id, shares=new_quota)
self.assertEqual(new_quota, int(updated["shares"]))
@ddt.data(
"share_groups",
"share_group_snapshots",
)
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
@testtools.skipUnless(
CONF.share.run_share_group_tests, 'Share Group tests disabled.')
@utils.skip_if_microversion_not_supported(SHARE_GROUPS_MICROVERSION)
def test_update_user_quota_share_groups(self, quota_key):
# Get current quotas
quotas = self.client.show_quotas(self.tenant_id, self.user_id)
new_quota = int(quotas[quota_key]) - 1
# Set new quota
updated = self.client.update_quotas(
self.tenant_id, self.user_id, **{quota_key: new_quota})
self.assertEqual(new_quota, int(updated[quota_key]))
def _create_share_type(self):
share_type = self.create_share_type(
data_utils.rand_name("tempest-manila"),
@ -280,44 +351,63 @@ class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
def test_reset_tenant_quotas(self):
# get default_quotas
# Get default_quotas
default = self.client.default_quotas(self.tenant_id)
# get current quotas
# Get current quotas
custom = self.client.show_quotas(self.tenant_id)
# make quotas for update
shares = int(custom["shares"]) + 2
snapshots = int(custom["snapshots"]) + 2
gigabytes = int(custom["gigabytes"]) + 2
snapshot_gigabytes = int(custom["snapshot_gigabytes"]) + 2
share_networks = int(custom["share_networks"]) + 2
# Make quotas for update
data = {
"shares": int(custom["shares"]) + 2,
"snapshots": int(custom["snapshots"]) + 2,
"gigabytes": int(custom["gigabytes"]) + 2,
"snapshot_gigabytes": int(custom["snapshot_gigabytes"]) + 2,
"share_networks": int(custom["share_networks"]) + 2,
}
if (utils.is_microversion_supported(SHARE_GROUPS_MICROVERSION) and
CONF.share.run_share_group_tests):
data["share_groups"] = int(custom["share_groups"]) + 2
data["share_group_snapshots"] = (
int(custom["share_group_snapshots"]) + 2)
# set new quota
updated = self.client.update_quotas(
self.tenant_id,
shares=shares,
snapshots=snapshots,
gigabytes=gigabytes,
snapshot_gigabytes=snapshot_gigabytes,
share_networks=share_networks)
self.assertEqual(shares, int(updated["shares"]))
self.assertEqual(snapshots, int(updated["snapshots"]))
self.assertEqual(gigabytes, int(updated["gigabytes"]))
self.assertEqual(snapshot_gigabytes,
int(updated["snapshot_gigabytes"]))
self.assertEqual(share_networks, int(updated["share_networks"]))
updated = self.client.update_quotas(self.tenant_id, **data)
self.assertEqual(data["shares"], int(updated["shares"]))
self.assertEqual(data["snapshots"], int(updated["snapshots"]))
self.assertEqual(data["gigabytes"], int(updated["gigabytes"]))
self.assertEqual(
data["snapshot_gigabytes"], int(updated["snapshot_gigabytes"]))
self.assertEqual(
data["share_networks"], int(updated["share_networks"]))
if (utils.is_microversion_supported(SHARE_GROUPS_MICROVERSION) and
CONF.share.run_share_group_tests):
self.assertEqual(
data["share_groups"], int(updated["share_groups"]))
self.assertEqual(
data["share_group_snapshots"],
int(updated["share_group_snapshots"]))
# reset customized quotas
# Reset customized quotas
self.client.reset_quotas(self.tenant_id)
# verify quotas
# Verify quotas
reseted = self.client.show_quotas(self.tenant_id)
self.assertEqual(int(default["shares"]), int(reseted["shares"]))
self.assertEqual(int(default["snapshots"]), int(reseted["snapshots"]))
self.assertEqual(int(default["gigabytes"]), int(reseted["gigabytes"]))
self.assertEqual(int(default["share_networks"]),
int(reseted["share_networks"]))
self.assertEqual(
int(default["snapshot_gigabytes"]),
int(reseted["snapshot_gigabytes"]))
self.assertEqual(
int(default["share_networks"]), int(reseted["share_networks"]))
if (utils.is_microversion_supported(SHARE_GROUPS_MICROVERSION) and
CONF.share.run_share_group_tests):
self.assertEqual(
int(default["share_groups"]), int(reseted["share_groups"]))
self.assertEqual(
int(default["share_group_snapshots"]),
int(reseted["share_group_snapshots"]))
@ddt.data(
('id', True),
@ -450,6 +540,29 @@ class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
self.assertEqual(-1, quotas.get('share_networks'))
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
@testtools.skipUnless(
CONF.share.run_share_group_tests, 'Share Group tests disabled.')
@utils.skip_if_microversion_not_supported(SHARE_GROUPS_MICROVERSION)
def test_unlimited_quota_for_share_groups(self):
self.client.update_quotas(self.tenant_id, share_groups=-1)
quotas = self.client.show_quotas(self.tenant_id)
self.assertEqual(-1, quotas.get('share_groups'))
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
@testtools.skipUnless(
CONF.share.run_share_group_tests, 'Share Group tests disabled.')
@utils.skip_if_microversion_not_supported(SHARE_GROUPS_MICROVERSION)
def test_unlimited_user_quota_for_share_group_snapshots(self):
self.client.update_quotas(
self.tenant_id, self.user_id, share_group_snapshots=-1)
quotas = self.client.show_quotas(self.tenant_id, self.user_id)
self.assertEqual(-1, quotas.get('share_group_snapshots'))
@ddt.data(11, -1)
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
def test_update_user_quotas_bigger_than_project_quota(self, user_quota):
@ -541,3 +654,82 @@ class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
for key in ('shares', 'gigabytes'):
self.assertEqual(0, quotas[key]['reserved'])
self.assertEqual(0, quotas[key]['in_use'])
def _check_sg_usages(self, quotas, in_use, limit):
"""Helper method for 'test_share_group_quotas_usages' test."""
self.assertEqual(0, int(quotas['share_groups']['reserved']))
self.assertEqual(in_use, int(quotas['share_groups']['in_use']))
self.assertEqual(limit, int(quotas['share_groups']['limit']))
def _check_sgs_usages(self, quotas, in_use):
"""Helper method for 'test_share_group_quotas_usages' test."""
self.assertEqual(0, int(quotas['share_group_snapshots']['reserved']))
self.assertEqual(
in_use, int(quotas['share_group_snapshots']['in_use']))
self.assertEqual(1, int(quotas['share_group_snapshots']['limit']))
def _check_usages(self, sg_in_use, sgs_in_use):
"""Helper method for 'test_share_group_quotas_usages' test."""
p_quotas = self.client.detail_quotas(tenant_id=self.tenant_id)
u_quotas = self.client.detail_quotas(
tenant_id=self.tenant_id, user_id=self.user_id)
self._check_sg_usages(p_quotas, sg_in_use, 3)
self._check_sg_usages(u_quotas, sg_in_use, 2)
self._check_sgs_usages(p_quotas, sgs_in_use)
self._check_sgs_usages(u_quotas, sgs_in_use)
@tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
@testtools.skipUnless(
CONF.share.run_share_group_tests, 'Share Group tests disabled.')
@base.skip_if_microversion_lt(SHARE_GROUPS_MICROVERSION)
def test_share_group_quotas_usages(self):
# Set quotas for project (3 SG, 1 SGS) and user (2 SG, 1 SGS)
self.client.update_quotas(
self.tenant_id, share_groups=3, share_group_snapshots=1)
self.client.update_quotas(
self.tenant_id, user_id=self.user_id,
share_groups=2, share_group_snapshots=1)
# Check usages, they should be 0s
self._check_usages(0, 0)
# Create SG1 and check usages
share_group1 = self.create_share_group(
cleanup_in_class=False, client=self.client)
self._check_usages(1, 0)
# Create SGS1 and check usages
sg_snapshot = self.create_share_group_snapshot_wait_for_active(
share_group1['id'], cleanup_in_class=False, client=self.client)
self._check_usages(1, 1)
# Create SG2 from SGS1 and check usages
share_group2 = self.create_share_group(
cleanup_in_class=False, client=self.client,
source_share_group_snapshot_id=sg_snapshot['id'])
self._check_usages(2, 1)
# Try create SGS2, fail, then check usages
self.assertRaises(
lib_exc.OverLimit,
self.create_share_group,
client=self.client, cleanup_in_class=False)
self._check_usages(2, 1)
# Delete SG2 and check usages
self.client.delete_share_group(share_group2['id'])
self.client.wait_for_resource_deletion(
share_group_id=share_group2['id'])
self._check_usages(1, 1)
# Delete SGS1 and check usages
self.client.delete_share_group_snapshot(sg_snapshot['id'])
self.client.wait_for_resource_deletion(
share_group_snapshot_id=sg_snapshot['id'])
self._check_usages(1, 0)
# Delete SG1 and check usages
self.client.delete_share_group(share_group1['id'])
self.client.wait_for_resource_deletion(
share_group_id=share_group1['id'])
self._check_usages(0, 0)

View File

@ -17,11 +17,15 @@ import ddt
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions as lib_exc
import testtools
from testtools import testcase as tc
from manila_tempest_tests.tests.api import base
from manila_tempest_tests import utils
CONF = config.CONF
PRE_SHARE_GROUPS_MICROVERSION = "2.39"
SHARE_GROUPS_MICROVERSION = "2.40"
@ddt.ddt
@ -49,50 +53,35 @@ class SharesAdminQuotasNegativeTest(base.BaseSharesAdminTest):
self.assertRaises(lib_exc.NotFound,
client.reset_quotas, "")
@ddt.data(
{"shares": -2},
{"snapshots": -2},
{"gigabytes": -2},
{"snapshot_gigabytes": -2},
{"share_networks": -2},
)
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
def test_update_shares_quota_with_wrong_data(self):
def test_update_quota_with_wrong_data(self, kwargs):
# -1 is acceptable value as unlimited
client = self.get_client_with_isolated_creds()
self.assertRaises(lib_exc.BadRequest,
client.update_quotas,
client.tenant_id,
shares=-2)
self.assertRaises(
lib_exc.BadRequest,
client.update_quotas, client.tenant_id, **kwargs)
@ddt.data(
{"share_groups": -2},
{"share_group_snapshots": -2},
)
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
def test_update_snapshots_quota_with_wrong_data(self):
@testtools.skipUnless(
CONF.share.run_share_group_tests, 'Share Group tests disabled.')
@utils.skip_if_microversion_not_supported(SHARE_GROUPS_MICROVERSION)
def test_update_sg_quota_with_wrong_data(self, kwargs):
# -1 is acceptable value as unlimited
client = self.get_client_with_isolated_creds()
self.assertRaises(lib_exc.BadRequest,
client.update_quotas,
client.tenant_id,
snapshots=-2)
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
def test_update_gigabytes_quota_with_wrong_data(self):
# -1 is acceptable value as unlimited
client = self.get_client_with_isolated_creds()
self.assertRaises(lib_exc.BadRequest,
client.update_quotas,
client.tenant_id,
gigabytes=-2)
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
def test_update_snapshot_gigabytes_quota_with_wrong_data(self):
# -1 is acceptable value as unlimited
client = self.get_client_with_isolated_creds()
self.assertRaises(lib_exc.BadRequest,
client.update_quotas,
client.tenant_id,
snapshot_gigabytes=-2)
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
def test_update_share_networks_quota_with_wrong_data(self):
# -1 is acceptable value as unlimited
client = self.get_client_with_isolated_creds()
self.assertRaises(lib_exc.BadRequest,
client.update_quotas,
client.tenant_id,
share_networks=-2)
client = self.get_client_with_isolated_creds(client_version='2')
self.assertRaises(
lib_exc.BadRequest,
client.update_quotas, client.tenant_id, **kwargs)
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
def test_create_share_with_size_bigger_than_quota(self):
@ -105,6 +94,21 @@ class SharesAdminQuotasNegativeTest(base.BaseSharesAdminTest):
self.create_share,
size=overquota)
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
@testtools.skipUnless(
CONF.share.run_share_group_tests, 'Share Group tests disabled.')
@utils.skip_if_microversion_not_supported(SHARE_GROUPS_MICROVERSION)
def test_create_share_group_with_exceeding_quota_limit(self):
client = self.get_client_with_isolated_creds(client_version='2')
client.update_quotas(client.tenant_id, share_groups=0)
# Try schedule share group creation
self.assertRaises(
lib_exc.OverLimit,
self.create_share_group,
client=client,
cleanup_in_class=False)
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
def test_try_set_user_quota_shares_bigger_than_tenant_quota(self):
client = self.get_client_with_isolated_creds()
@ -267,6 +271,41 @@ class SharesAdminQuotasNegativeTest(base.BaseSharesAdminTest):
share_networks=int(tenant_quotas["share_networks"]),
)
@ddt.data('share_groups', 'share_group_snapshots')
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
@base.skip_if_microversion_lt(SHARE_GROUPS_MICROVERSION)
def test_try_update_share_type_quota_for_share_groups(self, quota_name):
client = self.get_client_with_isolated_creds(client_version='2')
share_type = self._create_share_type()
tenant_quotas = client.show_quotas(client.tenant_id)
self.assertRaises(
lib_exc.BadRequest,
client.update_quotas,
client.tenant_id,
share_type=share_type["name"],
**{quota_name: int(tenant_quotas[quota_name])}
)
@ddt.data('share_groups', 'share_group_snapshots')
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
@base.skip_if_microversion_not_supported(PRE_SHARE_GROUPS_MICROVERSION)
@base.skip_if_microversion_not_supported(SHARE_GROUPS_MICROVERSION)
def test_share_group_quotas_using_too_old_microversion(self, quota_key):
client = self.get_client_with_isolated_creds(client_version='2')
tenant_quotas = client.show_quotas(
client.tenant_id, version=SHARE_GROUPS_MICROVERSION)
kwargs = {
"version": PRE_SHARE_GROUPS_MICROVERSION,
quota_key: tenant_quotas[quota_key],
}
self.assertRaises(
lib_exc.BadRequest,
client.update_quotas,
client.tenant_id,
**kwargs)
@ddt.data('show', 'reset', 'update')
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
@base.skip_if_microversion_lt("2.38")

View File

@ -0,0 +1,7 @@
---
features:
- Added quotas for amount of share groups and share group snapshots.
upgrade:
- Two new config options are available for setting default quotas for share
groups and share group snapshots - 'quota_share_groups' and
'quota_share_group_snapshots'.