summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/common/quota.py231
-rw-r--r--openstackclient/tests/functional/common/test_quota.py32
-rw-r--r--openstackclient/tests/unit/common/test_quota.py87
-rw-r--r--openstackclient/tests/unit/compute/v2/fakes.py32
-rw-r--r--openstackclient/tests/unit/network/v2/fakes.py23
5 files changed, 337 insertions, 68 deletions
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py
index 282ea428..dba6873f 100644
--- a/openstackclient/common/quota.py
+++ b/openstackclient/common/quota.py
@@ -97,12 +97,164 @@ def _xform_get_quota(data, value, keys):
return res
-class ListQuota(command.Lister):
- _description = _("List quotas for all projects "
- "with non-default quota values")
+class BaseQuota(object):
+ def _get_project(self, parsed_args):
+ if parsed_args.project is not None:
+ identity_client = self.app.client_manager.identity
+ project = utils.find_resource(
+ identity_client.projects,
+ parsed_args.project,
+ )
+ project_id = project.id
+ project_name = project.name
+ elif self.app.client_manager.auth_ref:
+ # Get the project from the current auth
+ project = self.app.client_manager.auth_ref
+ project_id = project.project_id
+ project_name = project.project_name
+ else:
+ project = None
+ project_id = None
+ project_name = None
+ project_info = {}
+ project_info['id'] = project_id
+ project_info['name'] = project_name
+ return project_info
+
+ def get_compute_quota(self, client, parsed_args):
+ quota_class = (
+ parsed_args.quota_class if 'quota_class' in parsed_args else False)
+ detail = parsed_args.detail if 'detail' in parsed_args else False
+ default = parsed_args.default if 'default' in parsed_args else False
+ try:
+ if quota_class:
+ quota = client.quota_classes.get(parsed_args.project)
+ else:
+ project_info = self._get_project(parsed_args)
+ project = project_info['id']
+ if default:
+ quota = client.quotas.defaults(project)
+ else:
+ quota = client.quotas.get(project, detail=detail)
+ except Exception as e:
+ if type(e).__name__ == 'EndpointNotFound':
+ return {}
+ else:
+ raise
+ return quota._info
+
+ def get_volume_quota(self, client, parsed_args):
+ quota_class = (
+ parsed_args.quota_class if 'quota_class' in parsed_args else False)
+ default = parsed_args.default if 'default' in parsed_args else False
+ try:
+ if quota_class:
+ quota = client.quota_classes.get(parsed_args.project)
+ else:
+ project_info = self._get_project(parsed_args)
+ project = project_info['id']
+ if default:
+ quota = client.quotas.defaults(project)
+ else:
+ quota = client.quotas.get(project)
+ except Exception as e:
+ if type(e).__name__ == 'EndpointNotFound':
+ return {}
+ else:
+ raise
+ return quota._info
+
+ def get_network_quota(self, parsed_args):
+ quota_class = (
+ parsed_args.quota_class if 'quota_class' in parsed_args else False)
+ detail = parsed_args.detail if 'detail' in parsed_args else False
+ default = parsed_args.default if 'default' in parsed_args else False
+ if quota_class:
+ return {}
+ if self.app.client_manager.is_network_endpoint_enabled():
+ project_info = self._get_project(parsed_args)
+ project = project_info['id']
+ client = self.app.client_manager.network
+ if default:
+ network_quota = client.get_quota_default(project)
+ if type(network_quota) is not dict:
+ network_quota = network_quota.to_dict()
+ else:
+ network_quota = client.get_quota(project,
+ details=detail)
+ if type(network_quota) is not dict:
+ network_quota = network_quota.to_dict()
+ if detail:
+ # NOTE(slaweq): Neutron returns values with key "used" but
+ # Nova for example returns same data with key "in_use"
+ # instead.
+ # Because of that we need to convert Neutron key to
+ # the same as is returned from Nova to make result
+ # more consistent
+ for key, values in network_quota.items():
+ if type(values) is dict and "used" in values:
+ values[u'in_use'] = values.pop("used")
+ network_quota[key] = values
+ return network_quota
+ else:
+ return {}
+
+
+class ListQuota(command.Lister, BaseQuota):
+ _description = _(
+ "List quotas for all projects with non-default quota values or "
+ "list detailed quota informations for requested project")
+
+ def _get_detailed_quotas(self, parsed_args):
+ columns = (
+ 'resource',
+ 'in_use',
+ 'reserved',
+ 'limit'
+ )
+ column_headers = (
+ 'Resource',
+ 'In Use',
+ 'Reserved',
+ 'Limit'
+ )
+ quotas = {}
+ if parsed_args.compute:
+ quotas.update(self.get_compute_quota(
+ self.app.client_manager.compute, parsed_args))
+ if parsed_args.network:
+ quotas.update(self.get_network_quota(parsed_args))
+
+ result = []
+ for resource, values in quotas.items():
+ # NOTE(slaweq): there is no detailed quotas info for some resources
+ # and it should't be displayed here
+ if type(values) is dict:
+ result.append({
+ 'resource': resource,
+ 'in_use': values.get('in_use'),
+ 'reserved': values.get('reserved'),
+ 'limit': values.get('limit')
+ })
+ return (column_headers,
+ (utils.get_dict_properties(
+ s, columns,
+ ) for s in result))
def get_parser(self, prog_name):
parser = super(ListQuota, self).get_parser(prog_name)
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_('List quotas for this project <project> (name or ID)'),
+ )
+ parser.add_argument(
+ '--detail',
+ dest='detail',
+ action='store_true',
+ default=False,
+ help=_('Show details about quotas usage')
+ )
option = parser.add_mutually_exclusive_group(required=True)
option.add_argument(
'--compute',
@@ -130,6 +282,8 @@ class ListQuota(command.Lister):
project_ids = [getattr(p, 'id', '') for p in projects]
if parsed_args.compute:
+ if parsed_args.detail:
+ return self._get_detailed_quotas(parsed_args)
compute_client = self.app.client_manager.compute
for p in project_ids:
try:
@@ -193,6 +347,9 @@ class ListQuota(command.Lister):
) for s in result))
if parsed_args.volume:
+ if parsed_args.detail:
+ LOG.warning("Volume service doesn't provide detailed quota"
+ " information")
volume_client = self.app.client_manager.volume
for p in project_ids:
try:
@@ -243,6 +400,8 @@ class ListQuota(command.Lister):
) for s in result))
if parsed_args.network:
+ if parsed_args.detail:
+ return self._get_detailed_quotas(parsed_args)
client = self.app.client_manager.network
for p in project_ids:
try:
@@ -410,7 +569,7 @@ class SetQuota(command.Command):
**network_kwargs)
-class ShowQuota(command.ShowOne):
+class ShowQuota(command.ShowOne, BaseQuota):
_description = _("Show quotas for project or class")
def get_parser(self, prog_name):
@@ -438,62 +597,6 @@ class ShowQuota(command.ShowOne):
)
return parser
- def _get_project(self, parsed_args):
- if parsed_args.project is not None:
- identity_client = self.app.client_manager.identity
- project = utils.find_resource(
- identity_client.projects,
- parsed_args.project,
- )
- project_id = project.id
- project_name = project.name
- elif self.app.client_manager.auth_ref:
- # Get the project from the current auth
- project = self.app.client_manager.auth_ref
- project_id = project.project_id
- project_name = project.project_name
- else:
- project = None
- project_id = None
- project_name = None
- project_info = {}
- project_info['id'] = project_id
- project_info['name'] = project_name
- return project_info
-
- def get_compute_volume_quota(self, client, parsed_args):
- try:
- if parsed_args.quota_class:
- quota = client.quota_classes.get(parsed_args.project)
- else:
- project_info = self._get_project(parsed_args)
- project = project_info['id']
- if parsed_args.default:
- quota = client.quotas.defaults(project)
- else:
- quota = client.quotas.get(project)
- except Exception as e:
- if type(e).__name__ == 'EndpointNotFound':
- return {}
- else:
- raise
- return quota._info
-
- def get_network_quota(self, parsed_args):
- if parsed_args.quota_class:
- return {}
- if self.app.client_manager.is_network_endpoint_enabled():
- project_info = self._get_project(parsed_args)
- project = project_info['id']
- client = self.app.client_manager.network
- if parsed_args.default:
- network_quota = client.get_quota_default(project)
- else:
- network_quota = client.get_quota(project)
- return network_quota
- else:
- return {}
-
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
@@ -504,10 +607,10 @@ class ShowQuota(command.ShowOne):
# does not exist. If this is determined to be the
# intended behaviour of the API we will validate
# the argument with Identity ourselves later.
- compute_quota_info = self.get_compute_volume_quota(compute_client,
- parsed_args)
- volume_quota_info = self.get_compute_volume_quota(volume_client,
- parsed_args)
+ compute_quota_info = self.get_compute_quota(compute_client,
+ parsed_args)
+ volume_quota_info = self.get_volume_quota(volume_client,
+ parsed_args)
network_quota_info = self.get_network_quota(parsed_args)
# NOTE(reedip): Remove the below check once requirement for
# Openstack SDK is fixed to version 0.9.12 and above
diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py
index 76c69a4d..85942281 100644
--- a/openstackclient/tests/functional/common/test_quota.py
+++ b/openstackclient/tests/functional/common/test_quota.py
@@ -31,6 +31,38 @@ class QuotaTests(base.TestCase):
cls.PROJECT_NAME =\
cls.get_openstack_configuration_value('auth.project_name')
+ def test_quota_list_details_compute(self):
+ expected_headers = ["Resource", "In Use", "Reserved", "Limit"]
+ cmd_output = json.loads(self.openstack(
+ 'quota list -f json --detail --compute'
+ ))
+ self.assertIsNotNone(cmd_output)
+ resources = []
+ for row in cmd_output:
+ row_headers = [str(r) for r in row.keys()]
+ self.assertEqual(sorted(expected_headers), sorted(row_headers))
+ resources.append(row['Resource'])
+ # Ensure that returned quota is compute quota
+ self.assertIn("instances", resources)
+ # and that there is no network quota here
+ self.assertNotIn("networks", resources)
+
+ def test_quota_list_details_network(self):
+ expected_headers = ["Resource", "In Use", "Reserved", "Limit"]
+ cmd_output = json.loads(self.openstack(
+ 'quota list -f json --detail --network'
+ ))
+ self.assertIsNotNone(cmd_output)
+ resources = []
+ for row in cmd_output:
+ row_headers = [str(r) for r in row.keys()]
+ self.assertEqual(sorted(expected_headers), sorted(row_headers))
+ resources.append(row['Resource'])
+ # Ensure that returned quota is network quota
+ self.assertIn("networks", resources)
+ # and that there is no compute quota here
+ self.assertNotIn("instances", resources)
+
def test_quota_list_network_option(self):
if not self.haz_network:
self.skipTest("No Network service present")
diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py
index 1a3da31d..4f9e321b 100644
--- a/openstackclient/tests/unit/common/test_quota.py
+++ b/openstackclient/tests/unit/common/test_quota.py
@@ -197,6 +197,85 @@ class TestQuotaList(TestQuota):
self.cmd = quota.ListQuota(self.app, None)
+ @staticmethod
+ def _get_detailed_reference_data(quota):
+ reference_data = []
+ for name, values in quota.to_dict().items():
+ if type(values) is dict:
+ if 'used' in values:
+ # For network quota it's "used" key instead of "in_use"
+ in_use = values['used']
+ else:
+ in_use = values['in_use']
+ resource_values = [
+ in_use,
+ values['reserved'],
+ values['limit']]
+ reference_data.append(tuple([name] + resource_values))
+ return reference_data
+
+ def test_quota_list_details_compute(self):
+ detailed_quota = (
+ compute_fakes.FakeQuota.create_one_comp_detailed_quota())
+
+ detailed_column_header = (
+ 'Resource',
+ 'In Use',
+ 'Reserved',
+ 'Limit',
+ )
+ detailed_reference_data = (
+ self._get_detailed_reference_data(detailed_quota))
+
+ self.compute.quotas.get = mock.Mock(return_value=detailed_quota)
+
+ arglist = [
+ '--detail', '--compute',
+ ]
+ verifylist = [
+ ('detail', True),
+ ('compute', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ ret_quotas = list(data)
+
+ self.assertEqual(detailed_column_header, columns)
+ self.assertEqual(
+ sorted(detailed_reference_data), sorted(ret_quotas))
+
+ def test_quota_list_details_network(self):
+ detailed_quota = (
+ network_fakes.FakeQuota.create_one_net_detailed_quota())
+
+ detailed_column_header = (
+ 'Resource',
+ 'In Use',
+ 'Reserved',
+ 'Limit',
+ )
+ detailed_reference_data = (
+ self._get_detailed_reference_data(detailed_quota))
+
+ self.network.get_quota = mock.Mock(return_value=detailed_quota)
+
+ arglist = [
+ '--detail', '--network',
+ ]
+ verifylist = [
+ ('detail', True),
+ ('network', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ ret_quotas = list(data)
+
+ self.assertEqual(detailed_column_header, columns)
+ self.assertEqual(
+ sorted(detailed_reference_data), sorted(ret_quotas))
+
def test_quota_list_compute(self):
# Two projects with non-default quotas
self.compute.quotas.get = mock.Mock(
@@ -827,13 +906,13 @@ class TestQuotaShow(TestQuota):
self.cmd.take_action(parsed_args)
self.compute_quotas_mock.get.assert_called_once_with(
- self.projects[0].id,
+ self.projects[0].id, detail=False
)
self.volume_quotas_mock.get.assert_called_once_with(
self.projects[0].id,
)
self.network.get_quota.assert_called_once_with(
- self.projects[0].id,
+ self.projects[0].id, details=False
)
self.assertNotCalled(self.network.get_quota_default)
@@ -889,12 +968,12 @@ class TestQuotaShow(TestQuota):
self.cmd.take_action(parsed_args)
self.compute_quotas_mock.get.assert_called_once_with(
- identity_fakes.project_id,
+ identity_fakes.project_id, detail=False
)
self.volume_quotas_mock.get.assert_called_once_with(
identity_fakes.project_id,
)
self.network.get_quota.assert_called_once_with(
- identity_fakes.project_id,
+ identity_fakes.project_id, details=False
)
self.assertNotCalled(self.network.get_quota_default)
diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py
index 38f4ff67..ee7d4983 100644
--- a/openstackclient/tests/unit/compute/v2/fakes.py
+++ b/openstackclient/tests/unit/compute/v2/fakes.py
@@ -1401,6 +1401,38 @@ class FakeQuota(object):
return quota
+ @staticmethod
+ def create_one_comp_detailed_quota(attrs=None):
+ """Create one quota"""
+
+ attrs = attrs or {}
+
+ quota_attrs = {
+ 'id': 'project-id-' + uuid.uuid4().hex,
+ 'cores': {'reserved': 0, 'in_use': 0, 'limit': 20},
+ 'fixed_ips': {'reserved': 0, 'in_use': 0, 'limit': 30},
+ 'injected_files': {'reserved': 0, 'in_use': 0, 'limit': 100},
+ 'injected_file_content_bytes': {
+ 'reserved': 0, 'in_use': 0, 'limit': 10240},
+ 'injected_file_path_bytes': {
+ 'reserved': 0, 'in_use': 0, 'limit': 255},
+ 'instances': {'reserved': 0, 'in_use': 0, 'limit': 50},
+ 'key_pairs': {'reserved': 0, 'in_use': 0, 'limit': 20},
+ 'metadata_items': {'reserved': 0, 'in_use': 0, 'limit': 10},
+ 'ram': {'reserved': 0, 'in_use': 0, 'limit': 51200},
+ 'server_groups': {'reserved': 0, 'in_use': 0, 'limit': 10},
+ 'server_group_members': {'reserved': 0, 'in_use': 0, 'limit': 10}
+ }
+
+ quota_attrs.update(attrs)
+ quota = fakes.FakeResource(
+ info=copy.deepcopy(quota_attrs),
+ loaded=True)
+
+ quota.project_id = quota_attrs['id']
+
+ return quota
+
class FakeLimits(object):
"""Fake limits"""
diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py
index 28e92d11..ee0919fd 100644
--- a/openstackclient/tests/unit/network/v2/fakes.py
+++ b/openstackclient/tests/unit/network/v2/fakes.py
@@ -1700,3 +1700,26 @@ class FakeQuota(object):
info=copy.deepcopy(quota_attrs),
loaded=True)
return quota
+
+ @staticmethod
+ def create_one_net_detailed_quota(attrs=None):
+ """Create one quota"""
+ attrs = attrs or {}
+
+ quota_attrs = {
+ 'floating_ips': {'used': 0, 'reserved': 0, 'limit': 20},
+ 'networks': {'used': 0, 'reserved': 0, 'limit': 25},
+ 'ports': {'used': 0, 'reserved': 0, 'limit': 11},
+ 'rbac_policies': {'used': 0, 'reserved': 0, 'limit': 15},
+ 'routers': {'used': 0, 'reserved': 0, 'limit': 40},
+ 'security_groups': {'used': 0, 'reserved': 0, 'limit': 10},
+ 'security_group_rules': {'used': 0, 'reserved': 0, 'limit': 100},
+ 'subnets': {'used': 0, 'reserved': 0, 'limit': 20},
+ 'subnet_pools': {'used': 0, 'reserved': 0, 'limit': 30}}
+
+ quota_attrs.update(attrs)
+
+ quota = fakes.FakeResource(
+ info=copy.deepcopy(quota_attrs),
+ loaded=True)
+ return quota