Merge "Implement support for project limits"
This commit is contained in:
commit
6469d86522
128
doc/source/cli/command-objects/limit.rst
Normal file
128
doc/source/cli/command-objects/limit.rst
Normal 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)
|
238
openstackclient/identity/v3/limit.py
Normal file
238
openstackclient/identity/v3/limit.py
Normal 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)
|
@ -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
|
||||
|
192
openstackclient/tests/functional/identity/v3/test_limit.py
Normal file
192
openstackclient/tests/functional/identity/v3/test_limit.py
Normal 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))
|
@ -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):
|
||||
|
382
openstackclient/tests/unit/identity/v3/test_limit.py
Normal file
382
openstackclient/tests/unit/identity/v3/test_limit.py
Normal 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))
|
@ -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.
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user