Merge "Implement support for project limits"

This commit is contained in:
Zuul 2018-07-25 00:52:42 +00:00 committed by Gerrit Code Review
commit 6469d86522
8 changed files with 1021 additions and 0 deletions

View File

@ -0,0 +1,128 @@
=====
limit
=====
Identity v3
Limits are used to specify project-specific limits thresholds of resources.
limit create
------------
Create a new limit
.. program:: limit create
.. code:: bash
openstack limit create
[--description <description>]
[--region <region>]
--project <project>
--service <service>
--resource-limit <resource-limit>
<resource-name>
.. option:: --description <description>
Useful description of the limit or its purpose
.. option:: --region <region>
Region that the limit should be applied to
.. describe:: --project <project>
The project that the limit applies to (required)
.. describe:: --service <service>
The service that is responsible for the resource being limited (required)
.. describe:: --resource-limit <resource-limit>
The limit to apply to the project (required)
.. describe:: <resource-name>
The name of the resource to limit (e.g. cores or volumes)
limit delete
------------
Delete project-specific limit(s)
.. program:: limit delete
.. code:: bash
openstack limit delete
<limit-id> [<limit-id> ...]
.. describe:: <limit-id>
Limit(s) to delete (ID)
limit list
----------
List project-specific limits
.. program:: limit list
.. code:: bash
openstack limit list
[--service <service>]
[--resource-name <resource-name>]
[--region <region>]
.. option:: --service <service>
The service to filter the response by (name or ID)
.. option:: --resource-name <resource-name>
The name of the resource to filter the response by
.. option:: --region <region>
The region name to filter the response by
limit show
----------
Display details about a limit
.. program:: limit show
.. code:: bash
openstack limit show
<limit-id>
.. describe:: <limit-id>
Limit to display (ID)
limit set
---------
Update a limit
.. program:: limit show
.. code:: bash
openstack limit set
[--description <description>]
[--resource-limit <resource-limit>]
<limit-id>
.. option:: --description <description>
Useful description of the limit or its purpose
.. option:: --resource-limit <resource-limit>
The limit to apply to the project
.. describe:: <limit-id>
Limit to update (ID)

View File

@ -0,0 +1,238 @@
# 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.
#
"""Limits action implementations."""
import logging
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
import six
from openstackclient.i18n import _
from openstackclient.identity import common as common_utils
LOG = logging.getLogger(__name__)
class CreateLimit(command.ShowOne):
_description = _("Create a limit")
def get_parser(self, prog_name):
parser = super(CreateLimit, self).get_parser(prog_name)
parser.add_argument(
'--description',
metavar='<description>',
help=_('Description of the limit'),
)
parser.add_argument(
'--region',
metavar='<region>',
help=_('Region for the limit to affect.'),
)
parser.add_argument(
'--project',
metavar='<project>',
required=True,
help=_('Project to associate the resource limit to'),
)
parser.add_argument(
'--service',
metavar='<service>',
required=True,
help=_('Service responsible for the resource to limit'),
)
parser.add_argument(
'--resource-limit',
metavar='<resource-limit>',
required=True,
type=int,
help=_('The resource limit for the project to assume'),
)
parser.add_argument(
'resource_name',
metavar='<resource-name>',
help=_('The name of the resource to limit'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
project = common_utils.find_project(
identity_client, parsed_args.project
)
service = common_utils.find_service(
identity_client, parsed_args.service
)
region = None
if parsed_args.region:
region = utils.find_resource(
identity_client.regions, parsed_args.region
)
limit = identity_client.limits.create(
project,
service,
parsed_args.resource_name,
parsed_args.resource_limit,
description=parsed_args.description,
region=region
)
limit._info.pop('links', None)
return zip(*sorted(six.iteritems(limit._info)))
class ListLimit(command.Lister):
_description = _("List limits")
def get_parser(self, prog_name):
parser = super(ListLimit, self).get_parser(prog_name)
parser.add_argument(
'--service',
metavar='<service>',
help=_('Service responsible for the resource to limit'),
)
parser.add_argument(
'--resource-name',
metavar='<resource-name>',
dest='resource_name',
help=_('The name of the resource to limit'),
)
parser.add_argument(
'--region',
metavar='<region>',
help=_('Region for the registered limit to affect.'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
service = None
if parsed_args.service:
service = common_utils.find_service(
identity_client, parsed_args.service
)
region = None
if parsed_args.region:
region = utils.find_resource(
identity_client.regions, parsed_args.region
)
limits = identity_client.limits.list(
service=service,
resource_name=parsed_args.resource_name,
region=region
)
columns = (
'ID', 'Project ID', 'Service ID', 'Resource Name',
'Resource Limit', 'Description', 'Region ID'
)
return (
columns,
(utils.get_item_properties(s, columns) for s in limits),
)
class ShowLimit(command.ShowOne):
_description = _("Display limit details")
def get_parser(self, prog_name):
parser = super(ShowLimit, self).get_parser(prog_name)
parser.add_argument(
'limit_id',
metavar='<limit-id>',
help=_('Limit to display (ID)'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
limit = identity_client.limits.get(parsed_args.limit_id)
limit._info.pop('links', None)
return zip(*sorted(six.iteritems(limit._info)))
class SetLimit(command.ShowOne):
_description = _("Update information about a limit")
def get_parser(self, prog_name):
parser = super(SetLimit, self).get_parser(prog_name)
parser.add_argument(
'limit_id',
metavar='<limit-id>',
help=_('Limit to update (ID)'),
)
parser.add_argument(
'--description',
metavar='<description>',
help=_('Description of the limit'),
)
parser.add_argument(
'--resource-limit',
metavar='<resource-limit>',
dest='resource_limit',
type=int,
help=_('The resource limit for the project to assume'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
limit = identity_client.limits.update(
parsed_args.limit_id,
description=parsed_args.description,
resource_limit=parsed_args.resource_limit
)
limit._info.pop('links', None)
return zip(*sorted(six.iteritems(limit._info)))
class DeleteLimit(command.Command):
_description = _("Delete a limit")
def get_parser(self, prog_name):
parser = super(DeleteLimit, self).get_parser(prog_name)
parser.add_argument(
'limit_id',
metavar='<limit-id>',
nargs="+",
help=_('Limit to delete (ID)'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
errors = 0
for limit_id in parsed_args.limit_id:
try:
identity_client.limits.delete(limit_id)
except Exception as e:
errors += 1
LOG.error(_("Failed to delete limit with ID "
"'%(id)s': %(e)s"),
{'id': limit_id, 'e': e})
if errors > 0:
total = len(parsed_args.limit_id)
msg = (_("%(errors)s of %(total)s limits failed to "
"delete.") % {'errors': errors, 'total': total})
raise exceptions.CommandError(msg)

View File

@ -59,6 +59,10 @@ class IdentityTests(base.TestCase):
REGISTERED_LIMIT_LIST_HEADERS = ['ID', 'Service ID', 'Resource Name',
'Default Limit', 'Description',
'Region ID']
LIMIT_FIELDS = ['id', 'project_id', 'service_id', 'resource_name',
'resource_limit', 'description', 'region_id']
LIMIT_LIST_HEADERS = ['ID', 'Project ID', 'Service ID', 'Resource Name',
'Resource Limit', 'Description', 'Region ID']
@classmethod
def setUpClass(cls):
@ -356,3 +360,42 @@ class IdentityTests(base.TestCase):
for k, v in d.iteritems():
if k == key:
return v
def _create_dummy_limit(self, add_clean_up=True):
registered_limit_id = self._create_dummy_registered_limit()
raw_output = self.openstack(
'registered limit show %s' % registered_limit_id
)
items = self.parse_show(raw_output)
resource_name = self._extract_value_from_items('resource_name', items)
service_id = self._extract_value_from_items('service_id', items)
resource_limit = 15
project_name = self._create_dummy_project()
raw_output = self.openstack('project show %s' % project_name)
items = self.parse_show(raw_output)
project_id = self._extract_value_from_items('id', items)
params = {
'project_id': project_id,
'service_id': service_id,
'resource_name': resource_name,
'resource_limit': resource_limit
}
raw_output = self.openstack(
'limit create'
' --project %(project_id)s'
' --service %(service_id)s'
' --resource-limit %(resource_limit)s'
' %(resource_name)s' % params
)
items = self.parse_show(raw_output)
limit_id = self._extract_value_from_items('id', items)
if add_clean_up:
self.addCleanup(self.openstack, 'limit delete %s' % limit_id)
self.assert_show_fields(items, self.LIMIT_FIELDS)
return limit_id

View File

@ -0,0 +1,192 @@
# 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.
from tempest.lib.common.utils import data_utils
from openstackclient.tests.functional.identity.v3 import common
class LimitTestCase(common.IdentityTests):
def test_limit_create_with_service_name(self):
registered_limit_id = self._create_dummy_registered_limit()
raw_output = self.openstack(
'registered limit show %s' % registered_limit_id
)
items = self.parse_show(raw_output)
service_id = self._extract_value_from_items('service_id', items)
resource_name = self._extract_value_from_items('resource_name', items)
raw_output = self.openstack('service show %s' % service_id)
items = self.parse_show(raw_output)
service_name = self._extract_value_from_items('name', items)
project_name = self._create_dummy_project()
raw_output = self.openstack('project show %s' % project_name)
items = self.parse_show(raw_output)
project_id = self._extract_value_from_items('id', items)
params = {
'project_id': project_id,
'service_name': service_name,
'resource_name': resource_name,
'resource_limit': 15
}
raw_output = self.openstack(
'limit create'
' --project %(project_id)s'
' --service %(service_name)s'
' --resource-limit %(resource_limit)s'
' %(resource_name)s' % params
)
items = self.parse_show(raw_output)
limit_id = self._extract_value_from_items('id', items)
self.addCleanup(self.openstack, 'limit delete %s' % limit_id)
self.assert_show_fields(items, self.LIMIT_FIELDS)
def test_limit_create_with_project_name(self):
registered_limit_id = self._create_dummy_registered_limit()
raw_output = self.openstack(
'registered limit show %s' % registered_limit_id
)
items = self.parse_show(raw_output)
service_id = self._extract_value_from_items('service_id', items)
resource_name = self._extract_value_from_items('resource_name', items)
raw_output = self.openstack('service show %s' % service_id)
items = self.parse_show(raw_output)
service_name = self._extract_value_from_items('name', items)
project_name = self._create_dummy_project()
params = {
'project_name': project_name,
'service_name': service_name,
'resource_name': resource_name,
'resource_limit': 15
}
raw_output = self.openstack(
'limit create'
' --project %(project_name)s'
' --service %(service_name)s'
' --resource-limit %(resource_limit)s'
' %(resource_name)s' % params
)
items = self.parse_show(raw_output)
limit_id = self._extract_value_from_items('id', items)
self.addCleanup(self.openstack, 'limit delete %s' % limit_id)
self.assert_show_fields(items, self.LIMIT_FIELDS)
registered_limit_id = self._create_dummy_registered_limit()
def test_limit_create_with_service_id(self):
self._create_dummy_limit()
def test_limit_create_with_project_id(self):
self._create_dummy_limit()
def test_limit_create_with_options(self):
registered_limit_id = self._create_dummy_registered_limit()
region_id = self._create_dummy_region()
params = {
'region_id': region_id,
'registered_limit_id': registered_limit_id
}
raw_output = self.openstack(
'registered limit set'
' %(registered_limit_id)s'
' --region %(region_id)s' % params
)
items = self.parse_show(raw_output)
service_id = self._extract_value_from_items('service_id', items)
resource_name = self._extract_value_from_items('resource_name', items)
project_name = self._create_dummy_project()
raw_output = self.openstack('project show %s' % project_name)
items = self.parse_show(raw_output)
project_id = self._extract_value_from_items('id', items)
description = data_utils.arbitrary_string()
params = {
'project_id': project_id,
'service_id': service_id,
'resource_name': resource_name,
'resource_limit': 15,
'region_id': region_id,
'description': description
}
raw_output = self.openstack(
'limit create'
' --project %(project_id)s'
' --service %(service_id)s'
' --resource-limit %(resource_limit)s'
' --region %(region_id)s'
' --description %(description)s'
' %(resource_name)s' % params
)
items = self.parse_show(raw_output)
limit_id = self._extract_value_from_items('id', items)
self.addCleanup(self.openstack, 'limit delete %s' % limit_id)
self.assert_show_fields(items, self.LIMIT_FIELDS)
def test_limit_show(self):
limit_id = self._create_dummy_limit()
raw_output = self.openstack('limit show %s' % limit_id)
items = self.parse_show(raw_output)
self.assert_show_fields(items, self.LIMIT_FIELDS)
def test_limit_set_description(self):
limit_id = self._create_dummy_limit()
params = {
'description': data_utils.arbitrary_string(),
'limit_id': limit_id
}
raw_output = self.openstack(
'limit set'
' --description %(description)s'
' %(limit_id)s' % params
)
items = self.parse_show(raw_output)
self.assert_show_fields(items, self.LIMIT_FIELDS)
def test_limit_set_resource_limit(self):
limit_id = self._create_dummy_limit()
params = {
'resource_limit': 5,
'limit_id': limit_id
}
raw_output = self.openstack(
'limit set'
' --resource-limit %(resource_limit)s'
' %(limit_id)s' % params
)
items = self.parse_show(raw_output)
self.assert_show_fields(items, self.LIMIT_FIELDS)
def test_limit_list(self):
self._create_dummy_limit()
raw_output = self.openstack('limit list')
items = self.parse_listing(raw_output)
self.assert_table_structure(items, self.LIMIT_LIST_HEADERS)
def test_limit_delete(self):
limit_id = self._create_dummy_limit(add_clean_up=False)
raw_output = self.openstack('limit delete %s' % limit_id)
self.assertEqual(0, len(raw_output))

View File

@ -507,6 +507,29 @@ REGISTERED_LIMIT_OPTIONS = {
'region_id': region_id
}
limit_id = 'limit-id'
limit_resource_limit = 15
limit_description = 'limit of foobars'
limit_resource_name = 'foobars'
LIMIT = {
'id': limit_id,
'project_id': project_id,
'resource_limit': limit_resource_limit,
'resource_name': limit_resource_name,
'service_id': service_id,
'description': None,
'region_id': None
}
LIMIT_OPTIONS = {
'id': limit_id,
'project_id': project_id,
'resource_limit': limit_resource_limit,
'resource_name': limit_resource_name,
'service_id': service_id,
'description': limit_description,
'region_id': region_id
}
def fake_auth_ref(fake_token, fake_service=None):
"""Create an auth_ref using keystoneauth's fixtures"""
@ -601,6 +624,8 @@ class FakeIdentityv3Client(object):
self.inference_rules.resource_class = fakes.FakeResource(None, {})
self.registered_limits = mock.Mock()
self.registered_limits.resource_class = fakes.FakeResource(None, {})
self.limits = mock.Mock()
self.limits.resource_class = fakes.FakeResource(None, {})
class FakeFederationManager(object):

View File

@ -0,0 +1,382 @@
# 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 copy
from keystoneauth1.exceptions import http as ksa_exceptions
from osc_lib import exceptions
from openstackclient.identity.v3 import limit
from openstackclient.tests.unit import fakes
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
class TestLimit(identity_fakes.TestIdentityv3):
def setUp(self):
super(TestLimit, self).setUp()
identity_manager = self.app.client_manager.identity
self.limit_mock = identity_manager.limits
self.services_mock = identity_manager.services
self.services_mock.reset_mock()
self.projects_mock = identity_manager.projects
self.projects_mock.reset_mock()
self.regions_mock = identity_manager.regions
self.regions_mock.reset_mock()
class TestLimitCreate(TestLimit):
def setUp(self):
super(TestLimitCreate, self).setUp()
self.service = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.SERVICE),
loaded=True
)
self.services_mock.get.return_value = self.service
self.project = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.PROJECT),
loaded=True
)
self.projects_mock.get.return_value = self.project
self.region = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.REGION),
loaded=True
)
self.regions_mock.get.return_value = self.region
self.cmd = limit.CreateLimit(self.app, None)
def test_limit_create_without_options(self):
self.limit_mock.create.return_value = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.LIMIT),
loaded=True
)
resource_limit = 15
arglist = [
'--project', identity_fakes.project_id,
'--service', identity_fakes.service_id,
'--resource-limit', str(resource_limit),
identity_fakes.limit_resource_name
]
verifylist = [
('project', identity_fakes.project_id),
('service', identity_fakes.service_id),
('resource_name', identity_fakes.limit_resource_name),
('resource_limit', resource_limit)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
kwargs = {'description': None, 'region': None}
self.limit_mock.create.assert_called_with(
self.project,
self.service,
identity_fakes.limit_resource_name,
resource_limit,
**kwargs
)
collist = ('description', 'id', 'project_id', 'region_id',
'resource_limit', 'resource_name', 'service_id')
self.assertEqual(collist, columns)
datalist = (
None,
identity_fakes.limit_id,
identity_fakes.project_id,
None,
resource_limit,
identity_fakes.limit_resource_name,
identity_fakes.service_id
)
self.assertEqual(datalist, data)
def test_limit_create_with_options(self):
self.limit_mock.create.return_value = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.LIMIT_OPTIONS),
loaded=True
)
resource_limit = 15
arglist = [
'--project', identity_fakes.project_id,
'--service', identity_fakes.service_id,
'--resource-limit', str(resource_limit),
'--region', identity_fakes.region_id,
'--description', identity_fakes.limit_description,
identity_fakes.limit_resource_name
]
verifylist = [
('project', identity_fakes.project_id),
('service', identity_fakes.service_id),
('resource_name', identity_fakes.limit_resource_name),
('resource_limit', resource_limit),
('region', identity_fakes.region_id),
('description', identity_fakes.limit_description)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
kwargs = {
'description': identity_fakes.limit_description,
'region': self.region
}
self.limit_mock.create.assert_called_with(
self.project,
self.service,
identity_fakes.limit_resource_name,
resource_limit,
**kwargs
)
collist = ('description', 'id', 'project_id', 'region_id',
'resource_limit', 'resource_name', 'service_id')
self.assertEqual(collist, columns)
datalist = (
identity_fakes.limit_description,
identity_fakes.limit_id,
identity_fakes.project_id,
identity_fakes.region_id,
resource_limit,
identity_fakes.limit_resource_name,
identity_fakes.service_id
)
self.assertEqual(datalist, data)
class TestLimitDelete(TestLimit):
def setUp(self):
super(TestLimitDelete, self).setUp()
self.cmd = limit.DeleteLimit(self.app, None)
def test_limit_delete(self):
self.limit_mock.delete.return_value = None
arglist = [identity_fakes.limit_id]
verifylist = [
('limit_id', [identity_fakes.limit_id])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.limit_mock.delete.assert_called_with(
identity_fakes.limit_id
)
self.assertIsNone(result)
def test_limit_delete_with_exception(self):
return_value = ksa_exceptions.NotFound()
self.limit_mock.delete.side_effect = return_value
arglist = ['fake-limit-id']
verifylist = [
('limit_id', ['fake-limit-id'])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
try:
self.cmd.take_action(parsed_args)
self.fail('CommandError should be raised.')
except exceptions.CommandError as e:
self.assertEqual(
'1 of 1 limits failed to delete.', str(e)
)
class TestLimitShow(TestLimit):
def setUp(self):
super(TestLimitShow, self).setUp()
self.limit_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.LIMIT),
loaded=True
)
self.cmd = limit.ShowLimit(self.app, None)
def test_limit_show(self):
arglist = [identity_fakes.limit_id]
verifylist = [('limit_id', identity_fakes.limit_id)]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.limit_mock.get.assert_called_with(identity_fakes.limit_id)
collist = (
'description', 'id', 'project_id', 'region_id', 'resource_limit',
'resource_name', 'service_id'
)
self.assertEqual(collist, columns)
datalist = (
None,
identity_fakes.limit_id,
identity_fakes.project_id,
None,
identity_fakes.limit_resource_limit,
identity_fakes.limit_resource_name,
identity_fakes.service_id
)
self.assertEqual(datalist, data)
class TestLimitSet(TestLimit):
def setUp(self):
super(TestLimitSet, self).setUp()
self.cmd = limit.SetLimit(self.app, None)
def test_limit_set_description(self):
limit = copy.deepcopy(identity_fakes.LIMIT)
limit['description'] = identity_fakes.limit_description
self.limit_mock.update.return_value = fakes.FakeResource(
None, limit, loaded=True
)
arglist = [
'--description', identity_fakes.limit_description,
identity_fakes.limit_id
]
verifylist = [
('description', identity_fakes.limit_description),
('limit_id', identity_fakes.limit_id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.limit_mock.update.assert_called_with(
identity_fakes.limit_id,
description=identity_fakes.limit_description,
resource_limit=None
)
collist = (
'description', 'id', 'project_id', 'region_id', 'resource_limit',
'resource_name', 'service_id'
)
self.assertEqual(collist, columns)
datalist = (
identity_fakes.limit_description,
identity_fakes.limit_id,
identity_fakes.project_id,
None,
identity_fakes.limit_resource_limit,
identity_fakes.limit_resource_name,
identity_fakes.service_id
)
self.assertEqual(datalist, data)
def test_limit_set_resource_limit(self):
resource_limit = 20
limit = copy.deepcopy(identity_fakes.LIMIT)
limit['resource_limit'] = resource_limit
self.limit_mock.update.return_value = fakes.FakeResource(
None, limit, loaded=True
)
arglist = [
'--resource-limit', str(resource_limit),
identity_fakes.limit_id
]
verifylist = [
('resource_limit', resource_limit),
('limit_id', identity_fakes.limit_id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.limit_mock.update.assert_called_with(
identity_fakes.limit_id,
description=None,
resource_limit=resource_limit
)
collist = (
'description', 'id', 'project_id', 'region_id', 'resource_limit',
'resource_name', 'service_id'
)
self.assertEqual(collist, columns)
datalist = (
None,
identity_fakes.limit_id,
identity_fakes.project_id,
None,
resource_limit,
identity_fakes.limit_resource_name,
identity_fakes.service_id
)
self.assertEqual(datalist, data)
class TestLimitList(TestLimit):
def setUp(self):
super(TestLimitList, self).setUp()
self.limit_mock.list.return_value = [
fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.LIMIT),
loaded=True
)
]
self.cmd = limit.ListLimit(self.app, None)
def test_limit_list(self):
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.limit_mock.list.assert_called_with(
service=None, resource_name=None, region=None
)
collist = (
'ID', 'Project ID', 'Service ID', 'Resource Name',
'Resource Limit', 'Description', 'Region ID'
)
self.assertEqual(collist, columns)
datalist = ((
identity_fakes.limit_id,
identity_fakes.project_id,
identity_fakes.service_id,
identity_fakes.limit_resource_name,
identity_fakes.limit_resource_limit,
None,
None
), )
self.assertEqual(datalist, tuple(data))

View File

@ -0,0 +1,7 @@
---
features:
- |
[`bp unified-limits <https://blueprints.launchpad.net/keystone/+spec/unified-limit>`_]
Support has been added for managing project-specific limits in keystone via
the ``limit`` command. Limits define limits of resources for projects to
consume once a limit has been registered.

View File

@ -268,6 +268,12 @@ openstack.identity.v3 =
implied_role_delete = openstackclient.identity.v3.implied_role:DeleteImpliedRole
implied_role_list = openstackclient.identity.v3.implied_role:ListImpliedRole
limit_create = openstackclient.identity.v3.limit:CreateLimit
limit_delete = openstackclient.identity.v3.limit:DeleteLimit
limit_list = openstackclient.identity.v3.limit:ListLimit
limit_set = openstackclient.identity.v3.limit:SetLimit
limit_show = openstackclient.identity.v3.limit:ShowLimit
mapping_create = openstackclient.identity.v3.mapping:CreateMapping
mapping_delete = openstackclient.identity.v3.mapping:DeleteMapping
mapping_list = openstackclient.identity.v3.mapping:ListMapping