Add quotas per share type
With this feature it will be possible to set quotas per share type for all existing quota resources. It is useful for deployments with multiple backends that are accessible via different share types. Also, fix one of existing DB migrations that hangs on PostgreSQL. APIImpact DocImpact Implements blueprint support-quotas-per-share-type Change-Id: I8472418c2eb363cf5a76c672c7fdea72f21e4f63
This commit is contained in:
parent
b089d1408a
commit
2df1a2fdcc
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue