Implement OSC quota set default and project mandatory

Added openstack command for updating the default quota
class using:

openstack share quota set default --class

This patch also changes the share quota commands to be
project parameter mandatory.

Partially-implements: bp openstack-client-support
Change-Id: I48c190e7e02f38695cdf319d5f80826d467d8b6e
This commit is contained in:
Felipe Rodrigues 2021-08-20 19:06:46 -03:00 committed by Victoria Martinez de la Cruz
parent 1ebaf0d025
commit f9aee522ef
3 changed files with 151 additions and 134 deletions
manilaclient
osc/v2
tests/unit/osc/v2

@ -18,30 +18,46 @@ from manilaclient import api_versions
from manilaclient.common._i18n import _ from manilaclient.common._i18n import _
def _check_user_id_and_share_type_args(user_id, share_type):
if user_id and share_type:
raise exceptions.CommandError(_(
"'user_id' and 'share_type' values are mutually exclusive. "
"one or both should be unset."))
class QuotaSet(command.Command): class QuotaSet(command.Command):
"""Set quotas for a project or project/user or project/share-type.""" """Set quotas for a project or project/user or project/share-type.
_description = _("Set Quota")
It can be used to set the default class for all projects.
"""
_description = _("Set Quota for a project, or project/user or "
"project/share-type or a class.")
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(QuotaSet, self).get_parser(prog_name) parser = super(QuotaSet, self).get_parser(prog_name)
quota_type = parser.add_mutually_exclusive_group()
parser.add_argument( parser.add_argument(
'--project', 'project',
metavar='<project>', metavar='<project/class>',
help=_('Name or ID of the project to set the quotas for.') help=_("A project (name/ID) or a class (e.g.: default).")
) )
parser.add_argument( quota_type.add_argument(
'--class',
dest='quota_class',
action='store_true',
default=False,
help=_("Update class quota to all projects. "
"Mutually exclusive with '--user' and '--share-type'.")
)
quota_type.add_argument(
'--user', '--user',
metavar='<user>', metavar='<user>',
default=None, default=None,
help=_("Name or ID of a user to set the quotas for. Optional. " help=_("Name or ID of a user to set the quotas for. "
"Mutually exclusive with '--share-type'.") "Mutually exclusive with '--share-type' and '--class'.")
)
quota_type.add_argument(
'--share-type',
metavar='<share-type>',
type=str,
default=None,
help=_("Name or ID of a share type to set the quotas for. "
"Mutually exclusive with '--user' and '--class'. "
"Available only for microversion >= 2.39")
) )
parser.add_argument( parser.add_argument(
'--shares', '--shares',
@ -83,7 +99,7 @@ class QuotaSet(command.Command):
metavar='<share-groups>', metavar='<share-groups>',
type=int, type=int,
default=None, default=None,
help=_('New value for the "share-groups" quota.' help=_('New value for the "share-groups" quota. '
'Available only for microversion >= 2.40') 'Available only for microversion >= 2.40')
) )
parser.add_argument( parser.add_argument(
@ -91,7 +107,8 @@ class QuotaSet(command.Command):
metavar='<share-group-snapshots>', metavar='<share-group-snapshots>',
type=int, type=int,
default=None, default=None,
help=_('New value for the "share-group-snapshots" quota.') help=_('New value for the "share-group-snapshots" quota. '
'Available only for microversion >= 2.40')
) )
parser.add_argument( parser.add_argument(
'--share-replicas', '--share-replicas',
@ -109,23 +126,6 @@ class QuotaSet(command.Command):
help=_("Capacity of share replicas in total. " help=_("Capacity of share replicas in total. "
"Available only for microversion >= 2.53") "Available only for microversion >= 2.53")
) )
parser.add_argument(
'--share-type',
metavar='<share-type>',
type=str,
default=None,
help=_("Name or ID of a share type to set the quotas for. "
"Optional. "
"Mutually exclusive with '--user'. "
"Available only for microversion >= 2.39")
)
parser.add_argument(
'--force',
dest='force',
action="store_true",
default=None,
help=_('Force update the quota.')
)
parser.add_argument( parser.add_argument(
'--per-share-gigabytes', '--per-share-gigabytes',
metavar='<per-share-gigabytes>', metavar='<per-share-gigabytes>',
@ -134,6 +134,14 @@ class QuotaSet(command.Command):
help=_("New value for the 'per-share-gigabytes' quota." help=_("New value for the 'per-share-gigabytes' quota."
"Available only for microversion >= 2.62") "Available only for microversion >= 2.62")
) )
parser.add_argument(
'--force',
dest='force',
action="store_true",
default=None,
help=_('Force update the quota. '
'Not applicable for class update.')
)
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
@ -146,9 +154,6 @@ class QuotaSet(command.Command):
identity_client.users, identity_client.users,
parsed_args.user).id parsed_args.user).id
_check_user_id_and_share_type_args(
user_id, parsed_args.share_type)
kwargs = { kwargs = {
"shares": parsed_args.shares, "shares": parsed_args.shares,
"snapshots": parsed_args.snapshots, "snapshots": parsed_args.snapshots,
@ -206,27 +211,33 @@ class QuotaSet(command.Command):
"'share-groups', 'share-group-snapshots', 'share-replicas', " "'share-groups', 'share-group-snapshots', 'share-replicas', "
"'replica-gigabytes', 'per-share-gigabytes'")) "'replica-gigabytes', 'per-share-gigabytes'"))
project_id = None if parsed_args.quota_class:
if parsed_args.project: kwargs.update({
"class_name": parsed_args.project,
})
try:
share_client.quota_classes.update(**kwargs)
except Exception as e:
raise exceptions.CommandError(_(
"Failed to set quotas for %s class: '%s'")
% (parsed_args.project, e))
else:
project_id = utils.find_resource( project_id = utils.find_resource(
identity_client.projects, identity_client.projects,
parsed_args.project parsed_args.project).id
).id
else:
project_id = self.app.client_manager.auth_ref.project_id
kwargs.update({ kwargs.update({
"tenant_id": project_id, "tenant_id": project_id,
"force": parsed_args.force, "force": parsed_args.force,
"user_id": user_id "user_id": user_id
}) })
try: try:
share_client.quotas.update(**kwargs) share_client.quotas.update(**kwargs)
except Exception as e: except Exception as e:
raise exceptions.CommandError(_( raise exceptions.CommandError(_(
"Failed to set quotas for project '%s' : '%s'") "Failed to set quotas for project '%s' : '%s'")
% (parsed_args.project, e)) % (parsed_args.project, e))
class QuotaShow(command.ShowOne): class QuotaShow(command.ShowOne):
@ -235,24 +246,25 @@ class QuotaShow(command.ShowOne):
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(QuotaShow, self).get_parser(prog_name) parser = super(QuotaShow, self).get_parser(prog_name)
quota_type = parser.add_mutually_exclusive_group()
parser.add_argument( parser.add_argument(
'--project', 'project',
metavar='<project>', metavar='<project>',
help=_('Name or ID of hte project to list quotas for.') help=_('Name or ID of the project to list quotas for.')
) )
parser.add_argument( quota_type.add_argument(
'--user', '--user',
metavar='<user>', metavar='<user>',
default=None, default=None,
help=_("Name or ID of user to list the quotas for. Optional. " help=_("Name or ID of user to list the quotas for. Optional. "
"Mutually exclusive with '--share-type'.") "Mutually exclusive with '--share-type'.")
) )
parser.add_argument( quota_type.add_argument(
'--share-type', '--share-type',
metavar='<share-type>', metavar='<share-type>',
type=str, type=str,
default=None, default=None,
help=_("UUID or name of a share type to list the quotas for. " help=_("Name or ID of a share type to list the quotas for. "
"Optional. " "Optional. "
"Mutually exclusive with '--user'. " "Mutually exclusive with '--user'. "
"Available only for microversion >= 2.39") "Available only for microversion >= 2.39")
@ -282,16 +294,9 @@ class QuotaShow(command.ShowOne):
identity_client.users, identity_client.users,
parsed_args.user).id parsed_args.user).id
_check_user_id_and_share_type_args( project_id = utils.find_resource(
user_id, parsed_args.share_type) identity_client.projects,
parsed_args.project).id
project_id = None
if parsed_args.project:
project_id = utils.find_resource(
identity_client.projects,
parsed_args.project).id
else:
project_id = self.app.client_manager.auth_ref.project_id
quotas = {} quotas = {}
if parsed_args.defaults: if parsed_args.defaults:
@ -331,19 +336,20 @@ class QuotaDelete(command.Command):
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(QuotaDelete, self).get_parser(prog_name) parser = super(QuotaDelete, self).get_parser(prog_name)
quota_type = parser.add_mutually_exclusive_group()
parser.add_argument( parser.add_argument(
'--project', 'project',
metavar='<project>', metavar='<project>',
help=_('Name or ID of the project to delete quotas for.') help=_('Name or ID of the project to delete quotas for.')
) )
parser.add_argument( quota_type.add_argument(
'--user', '--user',
metavar='<user>', metavar='<user>',
default=None, default=None,
help=_("Name or ID of user to delete the quotas for. Optional. " help=_("Name or ID of user to delete the quotas for. Optional. "
"Mutually exclusive with '--share-type'.") "Mutually exclusive with '--share-type'.")
) )
parser.add_argument( quota_type.add_argument(
'--share-type', '--share-type',
metavar='<share-type>', metavar='<share-type>',
type=str, type=str,
@ -365,16 +371,9 @@ class QuotaDelete(command.Command):
identity_client.users, identity_client.users,
parsed_args.user).id parsed_args.user).id
_check_user_id_and_share_type_args( project_id = utils.find_resource(
user_id, parsed_args.share_type) identity_client.projects,
parsed_args.project).id
project_id = None
if parsed_args.project:
project_id = utils.find_resource(
identity_client.projects,
parsed_args.project).id
else:
project_id = self.app.client_manager.auth_ref.project_id
kwargs = { kwargs = {
"tenant_id": project_id, "tenant_id": project_id,

@ -35,6 +35,7 @@ class FakeShareClient(object):
self.share_types = mock.Mock() self.share_types = mock.Mock()
self.share_type_access = mock.Mock() self.share_type_access = mock.Mock()
self.quotas = mock.Mock() self.quotas = mock.Mock()
self.quota_classes = mock.Mock()
self.share_snapshots = mock.Mock() self.share_snapshots = mock.Mock()
self.share_snapshot_export_locations = mock.Mock() self.share_snapshot_export_locations = mock.Mock()
self.share_snapshot_instances = mock.Mock() self.share_snapshot_instances = mock.Mock()

@ -31,6 +31,9 @@ class TestQuotas(manila_fakes.TestShare):
self.quotas_mock = self.app.client_manager.share.quotas self.quotas_mock = self.app.client_manager.share.quotas
self.quotas_mock.reset_mock() self.quotas_mock.reset_mock()
self.quota_classes_mock = self.app.client_manager.share.quota_classes
self.quota_classes_mock.reset_mock()
self.app.client_manager.share.api_version = api_versions.APIVersion( self.app.client_manager.share.api_version = api_versions.APIVersion(
api_versions.MAX_VERSION api_versions.MAX_VERSION
) )
@ -47,11 +50,44 @@ class TestQuotaSet(TestQuotas):
self.quotas_mock.update = mock.Mock() self.quotas_mock.update = mock.Mock()
self.quotas_mock.update.return_value = None self.quotas_mock.update.return_value = None
self.quota_classes_mock.update = mock.Mock()
self.quota_classes_mock.update.return_value = None
self.cmd = osc_quotas.QuotaSet(self.app, None) self.cmd = osc_quotas.QuotaSet(self.app, None)
def test_quota_set_default_class_shares(self):
arglist = [
'default',
'--class',
'--shares', '40'
]
verifylist = [
('project', 'default'),
('quota_class', True),
('shares', 40)
]
with mock.patch('osc_lib.utils.find_resource') as mock_find_resource:
mock_find_resource.return_value = self.project
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.quota_classes_mock.update.assert_called_with(
class_name='default',
gigabytes=None,
share_networks=None,
shares=40,
snapshot_gigabytes=None,
snapshots=None,
per_share_gigabytes=None)
self.assertIsNone(result)
mock_find_resource.assert_not_called()
self.quotas_mock.asert_not_called()
def test_quota_set_shares(self): def test_quota_set_shares(self):
arglist = [ arglist = [
'--project', self.project.id, self.project.id,
'--shares', '40' '--shares', '40'
] ]
verifylist = [ verifylist = [
@ -79,7 +115,7 @@ class TestQuotaSet(TestQuotas):
def test_quota_set_gigabytes(self): def test_quota_set_gigabytes(self):
arglist = [ arglist = [
'--project', self.project.id, self.project.id,
'--gigabytes', '1100' '--gigabytes', '1100'
] ]
verifylist = [ verifylist = [
@ -107,7 +143,7 @@ class TestQuotaSet(TestQuotas):
def test_quota_set_share_type(self): def test_quota_set_share_type(self):
arglist = [ arglist = [
'--project', self.project.id, self.project.id,
'--share-type', 'default' '--share-type', 'default'
] ]
verifylist = [ verifylist = [
@ -136,7 +172,7 @@ class TestQuotaSet(TestQuotas):
def test_quota_set_force(self): def test_quota_set_force(self):
arglist = [ arglist = [
'--project', self.project.id, self.project.id,
'--force', '--force',
'--shares', '40' '--shares', '40'
] ]
@ -170,7 +206,7 @@ class TestQuotaSet(TestQuotas):
) )
arglist = [ arglist = [
'--project', self.project.id, self.project.id,
'--share-groups', '40' '--share-groups', '40'
] ]
verifylist = [ verifylist = [
@ -182,9 +218,9 @@ class TestQuotaSet(TestQuotas):
self.assertRaises( self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args) exceptions.CommandError, self.cmd.take_action, parsed_args)
def test_quota_set_update_exception(self): def test_quota_set_update_project_exception(self):
arglist = [ arglist = [
'--project', self.project.id, self.project.id,
'--share-groups', '40', '--share-groups', '40',
'--share-group-snapshots', '40' '--share-group-snapshots', '40'
] ]
@ -199,9 +235,25 @@ class TestQuotaSet(TestQuotas):
self.assertRaises( self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args) exceptions.CommandError, self.cmd.take_action, parsed_args)
def test_quota_set_update_class_exception(self):
arglist = [
'default',
'--class',
'--gigabytes', '40'
]
verifylist = [
('project', 'default'),
('gigabytes', 40)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.quota_classes_mock.update.side_effect = BadRequest()
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
def test_quota_set_nothing_to_set_exception(self): def test_quota_set_nothing_to_set_exception(self):
arglist = [ arglist = [
'--project', self.project.id, self.project.id,
] ]
verifylist = [ verifylist = [
('project', self.project.id) ('project', self.project.id)
@ -217,7 +269,7 @@ class TestQuotaSet(TestQuotas):
) )
arglist = [ arglist = [
'--project', self.project.id, self.project.id,
'--share-replicas', '2', '--share-replicas', '2',
] ]
verifylist = [ verifylist = [
@ -248,7 +300,7 @@ class TestQuotaSet(TestQuotas):
self.app.client_manager.share.api_version = api_versions.APIVersion( self.app.client_manager.share.api_version = api_versions.APIVersion(
'2.51') '2.51')
arglist = [ arglist = [
'--project', self.project.id, self.project.id,
'--replica-gigabytes', '10', '--replica-gigabytes', '10',
] ]
verifylist = [ verifylist = [
@ -262,7 +314,7 @@ class TestQuotaSet(TestQuotas):
def test_quota_set_per_share_gigabytes(self): def test_quota_set_per_share_gigabytes(self):
arglist = [ arglist = [
'--project', self.project.id, self.project.id,
'--per-share-gigabytes', '10', '--per-share-gigabytes', '10',
] ]
verifylist = [ verifylist = [
@ -302,7 +354,7 @@ class TestQuotaShow(TestQuotas):
def test_quota_show(self): def test_quota_show(self):
arglist = [ arglist = [
'--project', self.project.id self.project.id
] ]
verifylist = [ verifylist = [
('project', self.project.id) ('project', self.project.id)
@ -329,7 +381,7 @@ class TestQuotaShow(TestQuotas):
) )
arglist = [ arglist = [
'--project', self.project.id, self.project.id,
'--share-type', 'default' '--share-type', 'default'
] ]
verifylist = [ verifylist = [
@ -341,25 +393,9 @@ class TestQuotaShow(TestQuotas):
self.assertRaises( self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args) exceptions.CommandError, self.cmd.take_action, parsed_args)
def test_quota_show_user_id_share_type_exception(self):
arglist = [
'--project', self.project.id,
'--share-type', 'default',
'--user', self.user.id
]
verifylist = [
('project', self.project.id),
('share_type', 'default'),
('user', self.user.id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
def test_quota_show_defaults(self): def test_quota_show_defaults(self):
arglist = [ arglist = [
'--project', self.project.id, self.project.id,
'--defaults' '--defaults'
] ]
verifylist = [ verifylist = [
@ -395,7 +431,7 @@ class TestQuotaDelete(TestQuotas):
def test_quota_delete(self): def test_quota_delete(self):
arglist = [ arglist = [
'--project', self.project.id self.project.id
] ]
verifylist = [ verifylist = [
('project', self.project.id) ('project', self.project.id)
@ -414,7 +450,7 @@ class TestQuotaDelete(TestQuotas):
def test_quota_delete_share_type(self): def test_quota_delete_share_type(self):
arglist = [ arglist = [
'--project', self.project.id, self.project.id,
'--share-type', 'default' '--share-type', 'default'
] ]
verifylist = [ verifylist = [
@ -440,7 +476,7 @@ class TestQuotaDelete(TestQuotas):
) )
arglist = [ arglist = [
'--project', self.project.id, self.project.id,
'--share-type', 'default' '--share-type', 'default'
] ]
verifylist = [ verifylist = [
@ -451,22 +487,3 @@ class TestQuotaDelete(TestQuotas):
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises( self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args) exceptions.CommandError, self.cmd.take_action, parsed_args)
def test_quota_delete_user_share_type_exeption(self):
arglist = [
'--project', self.project.id,
'--share-type', 'default',
'--user', self.user.id
]
verifylist = [
('project', self.project.id),
('share_type', 'default'),
('user', self.user.id)
]
with mock.patch('osc_lib.utils.find_resource') as mock_find_resource:
mock_find_resource.return_value = self.project
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)