summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2018-07-25 00:52:42 +0000
committerGerrit Code Review <review@openstack.org>2018-07-25 00:52:42 +0000
commit6469d86522f16a12e3437b70dc5f68a67314c691 (patch)
treee0299f3e87342fb1634ed07bab627793fafc4ea2
parentd177727a6e51500170338b4fd5a7e98179ff968f (diff)
parent735896eb1ad4543823008693c9d337691512f8bf (diff)
Merge "Implement support for project limits"
-rw-r--r--doc/source/cli/command-objects/limit.rst128
-rw-r--r--openstackclient/identity/v3/limit.py238
-rw-r--r--openstackclient/tests/functional/identity/v3/common.py43
-rw-r--r--openstackclient/tests/functional/identity/v3/test_limit.py192
-rw-r--r--openstackclient/tests/unit/identity/v3/fakes.py25
-rw-r--r--openstackclient/tests/unit/identity/v3/test_limit.py382
-rw-r--r--releasenotes/notes/bp-unified-limits-6c5fdb1c26805d86.yaml7
-rw-r--r--setup.cfg6
8 files changed, 1021 insertions, 0 deletions
diff --git a/doc/source/cli/command-objects/limit.rst b/doc/source/cli/command-objects/limit.rst
new file mode 100644
index 0000000..71cf2a4
--- /dev/null
+++ b/doc/source/cli/command-objects/limit.rst
@@ -0,0 +1,128 @@
1=====
2limit
3=====
4
5Identity v3
6
7Limits are used to specify project-specific limits thresholds of resources.
8
9limit create
10------------
11
12Create a new limit
13
14.. program:: limit create
15.. code:: bash
16
17 openstack limit create
18 [--description <description>]
19 [--region <region>]
20 --project <project>
21 --service <service>
22 --resource-limit <resource-limit>
23 <resource-name>
24
25.. option:: --description <description>
26
27 Useful description of the limit or its purpose
28
29.. option:: --region <region>
30
31 Region that the limit should be applied to
32
33.. describe:: --project <project>
34
35 The project that the limit applies to (required)
36
37.. describe:: --service <service>
38
39 The service that is responsible for the resource being limited (required)
40
41.. describe:: --resource-limit <resource-limit>
42
43 The limit to apply to the project (required)
44
45.. describe:: <resource-name>
46
47 The name of the resource to limit (e.g. cores or volumes)
48
49limit delete
50------------
51
52Delete project-specific limit(s)
53
54.. program:: limit delete
55.. code:: bash
56
57 openstack limit delete
58 <limit-id> [<limit-id> ...]
59
60.. describe:: <limit-id>
61
62 Limit(s) to delete (ID)
63
64limit list
65----------
66
67List project-specific limits
68
69.. program:: limit list
70.. code:: bash
71
72 openstack limit list
73 [--service <service>]
74 [--resource-name <resource-name>]
75 [--region <region>]
76
77.. option:: --service <service>
78
79 The service to filter the response by (name or ID)
80
81.. option:: --resource-name <resource-name>
82
83 The name of the resource to filter the response by
84
85.. option:: --region <region>
86
87 The region name to filter the response by
88
89limit show
90----------
91
92Display details about a limit
93
94.. program:: limit show
95.. code:: bash
96
97 openstack limit show
98 <limit-id>
99
100.. describe:: <limit-id>
101
102 Limit to display (ID)
103
104limit set
105---------
106
107Update a limit
108
109.. program:: limit show
110.. code:: bash
111
112 openstack limit set
113 [--description <description>]
114 [--resource-limit <resource-limit>]
115 <limit-id>
116
117
118.. option:: --description <description>
119
120 Useful description of the limit or its purpose
121
122.. option:: --resource-limit <resource-limit>
123
124 The limit to apply to the project
125
126.. describe:: <limit-id>
127
128 Limit to update (ID)
diff --git a/openstackclient/identity/v3/limit.py b/openstackclient/identity/v3/limit.py
new file mode 100644
index 0000000..c6f1cb1
--- /dev/null
+++ b/openstackclient/identity/v3/limit.py
@@ -0,0 +1,238 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12#
13
14"""Limits action implementations."""
15
16import logging
17
18from osc_lib.command import command
19from osc_lib import exceptions
20from osc_lib import utils
21import six
22
23from openstackclient.i18n import _
24from openstackclient.identity import common as common_utils
25
26LOG = logging.getLogger(__name__)
27
28
29class CreateLimit(command.ShowOne):
30 _description = _("Create a limit")
31
32 def get_parser(self, prog_name):
33 parser = super(CreateLimit, self).get_parser(prog_name)
34 parser.add_argument(
35 '--description',
36 metavar='<description>',
37 help=_('Description of the limit'),
38 )
39 parser.add_argument(
40 '--region',
41 metavar='<region>',
42 help=_('Region for the limit to affect.'),
43 )
44 parser.add_argument(
45 '--project',
46 metavar='<project>',
47 required=True,
48 help=_('Project to associate the resource limit to'),
49 )
50 parser.add_argument(
51 '--service',
52 metavar='<service>',
53 required=True,
54 help=_('Service responsible for the resource to limit'),
55 )
56 parser.add_argument(
57 '--resource-limit',
58 metavar='<resource-limit>',
59 required=True,
60 type=int,
61 help=_('The resource limit for the project to assume'),
62 )
63 parser.add_argument(
64 'resource_name',
65 metavar='<resource-name>',
66 help=_('The name of the resource to limit'),
67 )
68 return parser
69
70 def take_action(self, parsed_args):
71 identity_client = self.app.client_manager.identity
72
73 project = common_utils.find_project(
74 identity_client, parsed_args.project
75 )
76 service = common_utils.find_service(
77 identity_client, parsed_args.service
78 )
79 region = None
80 if parsed_args.region:
81 region = utils.find_resource(
82 identity_client.regions, parsed_args.region
83 )
84
85 limit = identity_client.limits.create(
86 project,
87 service,
88 parsed_args.resource_name,
89 parsed_args.resource_limit,
90 description=parsed_args.description,
91 region=region
92 )
93
94 limit._info.pop('links', None)
95 return zip(*sorted(six.iteritems(limit._info)))
96
97
98class ListLimit(command.Lister):
99 _description = _("List limits")
100
101 def get_parser(self, prog_name):
102 parser = super(ListLimit, self).get_parser(prog_name)
103 parser.add_argument(
104 '--service',
105 metavar='<service>',
106 help=_('Service responsible for the resource to limit'),
107 )
108 parser.add_argument(
109 '--resource-name',
110 metavar='<resource-name>',
111 dest='resource_name',
112 help=_('The name of the resource to limit'),
113 )
114 parser.add_argument(
115 '--region',
116 metavar='<region>',
117 help=_('Region for the registered limit to affect.'),
118 )
119 return parser
120
121 def take_action(self, parsed_args):
122 identity_client = self.app.client_manager.identity
123
124 service = None
125 if parsed_args.service:
126 service = common_utils.find_service(
127 identity_client, parsed_args.service
128 )
129 region = None
130 if parsed_args.region:
131 region = utils.find_resource(
132 identity_client.regions, parsed_args.region
133 )
134
135 limits = identity_client.limits.list(
136 service=service,
137 resource_name=parsed_args.resource_name,
138 region=region
139 )
140
141 columns = (
142 'ID', 'Project ID', 'Service ID', 'Resource Name',
143 'Resource Limit', 'Description', 'Region ID'
144 )
145 return (
146 columns,
147 (utils.get_item_properties(s, columns) for s in limits),
148 )
149
150
151class ShowLimit(command.ShowOne):
152 _description = _("Display limit details")
153
154 def get_parser(self, prog_name):
155 parser = super(ShowLimit, self).get_parser(prog_name)
156 parser.add_argument(
157 'limit_id',
158 metavar='<limit-id>',
159 help=_('Limit to display (ID)'),
160 )
161 return parser
162
163 def take_action(self, parsed_args):
164 identity_client = self.app.client_manager.identity
165 limit = identity_client.limits.get(parsed_args.limit_id)
166 limit._info.pop('links', None)
167 return zip(*sorted(six.iteritems(limit._info)))
168
169
170class SetLimit(command.ShowOne):
171 _description = _("Update information about a limit")
172
173 def get_parser(self, prog_name):
174 parser = super(SetLimit, self).get_parser(prog_name)
175 parser.add_argument(
176 'limit_id',
177 metavar='<limit-id>',
178 help=_('Limit to update (ID)'),
179 )
180 parser.add_argument(
181 '--description',
182 metavar='<description>',
183 help=_('Description of the limit'),
184 )
185 parser.add_argument(
186 '--resource-limit',
187 metavar='<resource-limit>',
188 dest='resource_limit',
189 type=int,
190 help=_('The resource limit for the project to assume'),
191 )
192 return parser
193
194 def take_action(self, parsed_args):
195 identity_client = self.app.client_manager.identity
196
197 limit = identity_client.limits.update(
198 parsed_args.limit_id,
199 description=parsed_args.description,
200 resource_limit=parsed_args.resource_limit
201 )
202
203 limit._info.pop('links', None)
204
205 return zip(*sorted(six.iteritems(limit._info)))
206
207
208class DeleteLimit(command.Command):
209 _description = _("Delete a limit")
210
211 def get_parser(self, prog_name):
212 parser = super(DeleteLimit, self).get_parser(prog_name)
213 parser.add_argument(
214 'limit_id',
215 metavar='<limit-id>',
216 nargs="+",
217 help=_('Limit to delete (ID)'),
218 )
219 return parser
220
221 def take_action(self, parsed_args):
222 identity_client = self.app.client_manager.identity
223
224 errors = 0
225 for limit_id in parsed_args.limit_id:
226 try:
227 identity_client.limits.delete(limit_id)
228 except Exception as e:
229 errors += 1
230 LOG.error(_("Failed to delete limit with ID "
231 "'%(id)s': %(e)s"),
232 {'id': limit_id, 'e': e})
233
234 if errors > 0:
235 total = len(parsed_args.limit_id)
236 msg = (_("%(errors)s of %(total)s limits failed to "
237 "delete.") % {'errors': errors, 'total': total})
238 raise exceptions.CommandError(msg)
diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py
index 525a31a..58468bc 100644
--- a/openstackclient/tests/functional/identity/v3/common.py
+++ b/openstackclient/tests/functional/identity/v3/common.py
@@ -59,6 +59,10 @@ class IdentityTests(base.TestCase):
59 REGISTERED_LIMIT_LIST_HEADERS = ['ID', 'Service ID', 'Resource Name', 59 REGISTERED_LIMIT_LIST_HEADERS = ['ID', 'Service ID', 'Resource Name',
60 'Default Limit', 'Description', 60 'Default Limit', 'Description',
61 'Region ID'] 61 'Region ID']
62 LIMIT_FIELDS = ['id', 'project_id', 'service_id', 'resource_name',
63 'resource_limit', 'description', 'region_id']
64 LIMIT_LIST_HEADERS = ['ID', 'Project ID', 'Service ID', 'Resource Name',
65 'Resource Limit', 'Description', 'Region ID']
62 66
63 @classmethod 67 @classmethod
64 def setUpClass(cls): 68 def setUpClass(cls):
@@ -356,3 +360,42 @@ class IdentityTests(base.TestCase):
356 for k, v in d.iteritems(): 360 for k, v in d.iteritems():
357 if k == key: 361 if k == key:
358 return v 362 return v
363
364 def _create_dummy_limit(self, add_clean_up=True):
365 registered_limit_id = self._create_dummy_registered_limit()
366
367 raw_output = self.openstack(
368 'registered limit show %s' % registered_limit_id
369 )
370 items = self.parse_show(raw_output)
371 resource_name = self._extract_value_from_items('resource_name', items)
372 service_id = self._extract_value_from_items('service_id', items)
373 resource_limit = 15
374
375 project_name = self._create_dummy_project()
376 raw_output = self.openstack('project show %s' % project_name)
377 items = self.parse_show(raw_output)
378 project_id = self._extract_value_from_items('id', items)
379
380 params = {
381 'project_id': project_id,
382 'service_id': service_id,
383 'resource_name': resource_name,
384 'resource_limit': resource_limit
385 }
386
387 raw_output = self.openstack(
388 'limit create'
389 ' --project %(project_id)s'
390 ' --service %(service_id)s'
391 ' --resource-limit %(resource_limit)s'
392 ' %(resource_name)s' % params
393 )
394 items = self.parse_show(raw_output)
395 limit_id = self._extract_value_from_items('id', items)
396
397 if add_clean_up:
398 self.addCleanup(self.openstack, 'limit delete %s' % limit_id)
399
400 self.assert_show_fields(items, self.LIMIT_FIELDS)
401 return limit_id
diff --git a/openstackclient/tests/functional/identity/v3/test_limit.py b/openstackclient/tests/functional/identity/v3/test_limit.py
new file mode 100644
index 0000000..03bcb06
--- /dev/null
+++ b/openstackclient/tests/functional/identity/v3/test_limit.py
@@ -0,0 +1,192 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13from tempest.lib.common.utils import data_utils
14
15from openstackclient.tests.functional.identity.v3 import common
16
17
18class LimitTestCase(common.IdentityTests):
19
20 def test_limit_create_with_service_name(self):
21 registered_limit_id = self._create_dummy_registered_limit()
22 raw_output = self.openstack(
23 'registered limit show %s' % registered_limit_id
24 )
25 items = self.parse_show(raw_output)
26 service_id = self._extract_value_from_items('service_id', items)
27 resource_name = self._extract_value_from_items('resource_name', items)
28
29 raw_output = self.openstack('service show %s' % service_id)
30 items = self.parse_show(raw_output)
31 service_name = self._extract_value_from_items('name', items)
32
33 project_name = self._create_dummy_project()
34 raw_output = self.openstack('project show %s' % project_name)
35 items = self.parse_show(raw_output)
36 project_id = self._extract_value_from_items('id', items)
37
38 params = {
39 'project_id': project_id,
40 'service_name': service_name,
41 'resource_name': resource_name,
42 'resource_limit': 15
43 }
44 raw_output = self.openstack(
45 'limit create'
46 ' --project %(project_id)s'
47 ' --service %(service_name)s'
48 ' --resource-limit %(resource_limit)s'
49 ' %(resource_name)s' % params
50 )
51 items = self.parse_show(raw_output)
52 limit_id = self._extract_value_from_items('id', items)
53 self.addCleanup(self.openstack, 'limit delete %s' % limit_id)
54
55 self.assert_show_fields(items, self.LIMIT_FIELDS)
56
57 def test_limit_create_with_project_name(self):
58 registered_limit_id = self._create_dummy_registered_limit()
59 raw_output = self.openstack(
60 'registered limit show %s' % registered_limit_id
61 )
62 items = self.parse_show(raw_output)
63 service_id = self._extract_value_from_items('service_id', items)
64 resource_name = self._extract_value_from_items('resource_name', items)
65
66 raw_output = self.openstack('service show %s' % service_id)
67 items = self.parse_show(raw_output)
68 service_name = self._extract_value_from_items('name', items)
69
70 project_name = self._create_dummy_project()
71
72 params = {
73 'project_name': project_name,
74 'service_name': service_name,
75 'resource_name': resource_name,
76 'resource_limit': 15
77 }
78 raw_output = self.openstack(
79 'limit create'
80 ' --project %(project_name)s'
81 ' --service %(service_name)s'
82 ' --resource-limit %(resource_limit)s'
83 ' %(resource_name)s' % params
84 )
85 items = self.parse_show(raw_output)
86 limit_id = self._extract_value_from_items('id', items)
87 self.addCleanup(self.openstack, 'limit delete %s' % limit_id)
88
89 self.assert_show_fields(items, self.LIMIT_FIELDS)
90 registered_limit_id = self._create_dummy_registered_limit()
91
92 def test_limit_create_with_service_id(self):
93 self._create_dummy_limit()
94
95 def test_limit_create_with_project_id(self):
96 self._create_dummy_limit()
97
98 def test_limit_create_with_options(self):
99 registered_limit_id = self._create_dummy_registered_limit()
100 region_id = self._create_dummy_region()
101
102 params = {
103 'region_id': region_id,
104 'registered_limit_id': registered_limit_id
105 }
106
107 raw_output = self.openstack(
108 'registered limit set'
109 ' %(registered_limit_id)s'
110 ' --region %(region_id)s' % params
111 )
112 items = self.parse_show(raw_output)
113 service_id = self._extract_value_from_items('service_id', items)
114 resource_name = self._extract_value_from_items('resource_name', items)
115
116 project_name = self._create_dummy_project()
117 raw_output = self.openstack('project show %s' % project_name)
118 items = self.parse_show(raw_output)
119 project_id = self._extract_value_from_items('id', items)
120 description = data_utils.arbitrary_string()
121
122 params = {
123 'project_id': project_id,
124 'service_id': service_id,
125 'resource_name': resource_name,
126 'resource_limit': 15,
127 'region_id': region_id,
128 'description': description
129 }
130 raw_output = self.openstack(
131 'limit create'
132 ' --project %(project_id)s'
133 ' --service %(service_id)s'
134 ' --resource-limit %(resource_limit)s'
135 ' --region %(region_id)s'
136 ' --description %(description)s'
137 ' %(resource_name)s' % params
138 )
139 items = self.parse_show(raw_output)
140 limit_id = self._extract_value_from_items('id', items)
141 self.addCleanup(self.openstack, 'limit delete %s' % limit_id)
142
143 self.assert_show_fields(items, self.LIMIT_FIELDS)
144
145 def test_limit_show(self):
146 limit_id = self._create_dummy_limit()
147 raw_output = self.openstack('limit show %s' % limit_id)
148 items = self.parse_show(raw_output)
149 self.assert_show_fields(items, self.LIMIT_FIELDS)
150
151 def test_limit_set_description(self):
152 limit_id = self._create_dummy_limit()
153
154 params = {
155 'description': data_utils.arbitrary_string(),
156 'limit_id': limit_id
157 }
158
159 raw_output = self.openstack(
160 'limit set'
161 ' --description %(description)s'
162 ' %(limit_id)s' % params
163 )
164 items = self.parse_show(raw_output)
165 self.assert_show_fields(items, self.LIMIT_FIELDS)
166
167 def test_limit_set_resource_limit(self):
168 limit_id = self._create_dummy_limit()
169
170 params = {
171 'resource_limit': 5,
172 'limit_id': limit_id
173 }
174
175 raw_output = self.openstack(
176 'limit set'
177 ' --resource-limit %(resource_limit)s'
178 ' %(limit_id)s' % params
179 )
180 items = self.parse_show(raw_output)
181 self.assert_show_fields(items, self.LIMIT_FIELDS)
182
183 def test_limit_list(self):
184 self._create_dummy_limit()
185 raw_output = self.openstack('limit list')
186 items = self.parse_listing(raw_output)
187 self.assert_table_structure(items, self.LIMIT_LIST_HEADERS)
188
189 def test_limit_delete(self):
190 limit_id = self._create_dummy_limit(add_clean_up=False)
191 raw_output = self.openstack('limit delete %s' % limit_id)
192 self.assertEqual(0, len(raw_output))
diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py
index 3cae451..27ee9fd 100644
--- a/openstackclient/tests/unit/identity/v3/fakes.py
+++ b/openstackclient/tests/unit/identity/v3/fakes.py
@@ -507,6 +507,29 @@ REGISTERED_LIMIT_OPTIONS = {
507 'region_id': region_id 507 'region_id': region_id
508} 508}
509 509
510limit_id = 'limit-id'
511limit_resource_limit = 15
512limit_description = 'limit of foobars'
513limit_resource_name = 'foobars'
514LIMIT = {
515 'id': limit_id,
516 'project_id': project_id,
517 'resource_limit': limit_resource_limit,
518 'resource_name': limit_resource_name,
519 'service_id': service_id,
520 'description': None,
521 'region_id': None
522}
523LIMIT_OPTIONS = {
524 'id': limit_id,
525 'project_id': project_id,
526 'resource_limit': limit_resource_limit,
527 'resource_name': limit_resource_name,
528 'service_id': service_id,
529 'description': limit_description,
530 'region_id': region_id
531}
532
510 533
511def fake_auth_ref(fake_token, fake_service=None): 534def fake_auth_ref(fake_token, fake_service=None):
512 """Create an auth_ref using keystoneauth's fixtures""" 535 """Create an auth_ref using keystoneauth's fixtures"""
@@ -601,6 +624,8 @@ class FakeIdentityv3Client(object):
601 self.inference_rules.resource_class = fakes.FakeResource(None, {}) 624 self.inference_rules.resource_class = fakes.FakeResource(None, {})
602 self.registered_limits = mock.Mock() 625 self.registered_limits = mock.Mock()
603 self.registered_limits.resource_class = fakes.FakeResource(None, {}) 626 self.registered_limits.resource_class = fakes.FakeResource(None, {})
627 self.limits = mock.Mock()
628 self.limits.resource_class = fakes.FakeResource(None, {})
604 629
605 630
606class FakeFederationManager(object): 631class FakeFederationManager(object):
diff --git a/openstackclient/tests/unit/identity/v3/test_limit.py b/openstackclient/tests/unit/identity/v3/test_limit.py
new file mode 100644
index 0000000..44c0358
--- /dev/null
+++ b/openstackclient/tests/unit/identity/v3/test_limit.py
@@ -0,0 +1,382 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import copy
14
15from keystoneauth1.exceptions import http as ksa_exceptions
16from osc_lib import exceptions
17
18from openstackclient.identity.v3 import limit
19from openstackclient.tests.unit import fakes
20from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
21
22
23class TestLimit(identity_fakes.TestIdentityv3):
24
25 def setUp(self):
26 super(TestLimit, self).setUp()
27
28 identity_manager = self.app.client_manager.identity
29
30 self.limit_mock = identity_manager.limits
31
32 self.services_mock = identity_manager.services
33 self.services_mock.reset_mock()
34
35 self.projects_mock = identity_manager.projects
36 self.projects_mock.reset_mock()
37
38 self.regions_mock = identity_manager.regions
39 self.regions_mock.reset_mock()
40
41
42class TestLimitCreate(TestLimit):
43
44 def setUp(self):
45 super(TestLimitCreate, self).setUp()
46
47 self.service = fakes.FakeResource(
48 None,
49 copy.deepcopy(identity_fakes.SERVICE),
50 loaded=True
51 )
52 self.services_mock.get.return_value = self.service
53
54 self.project = fakes.FakeResource(
55 None,
56 copy.deepcopy(identity_fakes.PROJECT),
57 loaded=True
58 )
59 self.projects_mock.get.return_value = self.project
60
61 self.region = fakes.FakeResource(
62 None,
63 copy.deepcopy(identity_fakes.REGION),
64 loaded=True
65 )
66 self.regions_mock.get.return_value = self.region
67
68 self.cmd = limit.CreateLimit(self.app, None)
69
70 def test_limit_create_without_options(self):
71 self.limit_mock.create.return_value = fakes.FakeResource(
72 None,
73 copy.deepcopy(identity_fakes.LIMIT),
74 loaded=True
75 )
76
77 resource_limit = 15
78 arglist = [
79 '--project', identity_fakes.project_id,
80 '--service', identity_fakes.service_id,
81 '--resource-limit', str(resource_limit),
82 identity_fakes.limit_resource_name
83 ]
84 verifylist = [
85 ('project', identity_fakes.project_id),
86 ('service', identity_fakes.service_id),
87 ('resource_name', identity_fakes.limit_resource_name),
88 ('resource_limit', resource_limit)
89 ]
90 parsed_args = self.check_parser(self.cmd, arglist, verifylist)
91
92 columns, data = self.cmd.take_action(parsed_args)
93
94 kwargs = {'description': None, 'region': None}
95 self.limit_mock.create.assert_called_with(
96 self.project,
97 self.service,
98 identity_fakes.limit_resource_name,
99 resource_limit,
100 **kwargs
101 )
102
103 collist = ('description', 'id', 'project_id', 'region_id',
104 'resource_limit', 'resource_name', 'service_id')
105 self.assertEqual(collist, columns)
106 datalist = (
107 None,
108 identity_fakes.limit_id,
109 identity_fakes.project_id,
110 None,
111 resource_limit,
112 identity_fakes.limit_resource_name,
113 identity_fakes.service_id
114 )
115 self.assertEqual(datalist, data)
116
117 def test_limit_create_with_options(self):
118 self.limit_mock.create.return_value = fakes.FakeResource(
119 None,
120 copy.deepcopy(identity_fakes.LIMIT_OPTIONS),
121 loaded=True
122 )
123
124 resource_limit = 15
125 arglist = [
126 '--project', identity_fakes.project_id,
127 '--service', identity_fakes.service_id,
128 '--resource-limit', str(resource_limit),
129 '--region', identity_fakes.region_id,
130 '--description', identity_fakes.limit_description,
131 identity_fakes.limit_resource_name
132 ]
133 verifylist = [
134 ('project', identity_fakes.project_id),
135 ('service', identity_fakes.service_id),
136 ('resource_name', identity_fakes.limit_resource_name),
137 ('resource_limit', resource_limit),
138 ('region', identity_fakes.region_id),
139 ('description', identity_fakes.limit_description)
140 ]
141 parsed_args = self.check_parser(self.cmd, arglist, verifylist)
142
143 columns, data = self.cmd.take_action(parsed_args)
144
145 kwargs = {
146 'description': identity_fakes.limit_description,
147 'region': self.region
148 }
149 self.limit_mock.create.assert_called_with(
150 self.project,
151 self.service,
152 identity_fakes.limit_resource_name,
153 resource_limit,
154 **kwargs
155 )
156
157 collist = ('description', 'id', 'project_id', 'region_id',
158 'resource_limit', 'resource_name', 'service_id')
159 self.assertEqual(collist, columns)
160 datalist = (
161 identity_fakes.limit_description,
162 identity_fakes.limit_id,
163 identity_fakes.project_id,
164 identity_fakes.region_id,
165 resource_limit,
166 identity_fakes.limit_resource_name,
167 identity_fakes.service_id
168 )
169 self.assertEqual(datalist, data)
170
171
172class TestLimitDelete(TestLimit):
173
174 def setUp(self):
175 super(TestLimitDelete, self).setUp()
176 self.cmd = limit.DeleteLimit(self.app, None)
177
178 def test_limit_delete(self):
179 self.limit_mock.delete.return_value = None
180
181 arglist = [identity_fakes.limit_id]
182 verifylist = [
183 ('limit_id', [identity_fakes.limit_id])
184 ]
185 parsed_args = self.check_parser(self.cmd, arglist, verifylist)
186
187 result = self.cmd.take_action(parsed_args)
188
189 self.limit_mock.delete.assert_called_with(
190 identity_fakes.limit_id
191 )
192 self.assertIsNone(result)
193
194 def test_limit_delete_with_exception(self):
195 return_value = ksa_exceptions.NotFound()
196 self.limit_mock.delete.side_effect = return_value
197
198 arglist = ['fake-limit-id']
199 verifylist = [
200 ('limit_id', ['fake-limit-id'])
201 ]
202 parsed_args = self.check_parser(self.cmd, arglist, verifylist)
203
204 try:
205 self.cmd.take_action(parsed_args)
206 self.fail('CommandError should be raised.')
207 except exceptions.CommandError as e:
208 self.assertEqual(
209 '1 of 1 limits failed to delete.', str(e)
210 )
211
212
213class TestLimitShow(TestLimit):
214
215 def setUp(self):
216 super(TestLimitShow, self).setUp()
217
218 self.limit_mock.get.return_value = fakes.FakeResource(
219 None,
220 copy.deepcopy(identity_fakes.LIMIT),
221 loaded=True
222 )
223
224 self.cmd = limit.ShowLimit(self.app, None)
225
226 def test_limit_show(self):
227 arglist = [identity_fakes.limit_id]
228 verifylist = [('limit_id', identity_fakes.limit_id)]
229 parsed_args = self.check_parser(self.cmd, arglist, verifylist)
230
231 columns, data = self.cmd.take_action(parsed_args)
232
233 self.limit_mock.get.assert_called_with(identity_fakes.limit_id)
234
235 collist = (
236 'description', 'id', 'project_id', 'region_id', 'resource_limit',
237 'resource_name', 'service_id'
238 )
239 self.assertEqual(collist, columns)
240 datalist = (
241 None,
242 identity_fakes.limit_id,
243 identity_fakes.project_id,
244 None,
245 identity_fakes.limit_resource_limit,
246 identity_fakes.limit_resource_name,
247 identity_fakes.service_id
248 )
249 self.assertEqual(datalist, data)
250
251
252class TestLimitSet(TestLimit):
253
254 def setUp(self):
255 super(TestLimitSet, self).setUp()
256 self.cmd = limit.SetLimit(self.app, None)
257
258 def test_limit_set_description(self):
259 limit = copy.deepcopy(identity_fakes.LIMIT)
260 limit['description'] = identity_fakes.limit_description
261 self.limit_mock.update.return_value = fakes.FakeResource(
262 None, limit, loaded=True
263 )
264
265 arglist = [
266 '--description', identity_fakes.limit_description,
267 identity_fakes.limit_id
268 ]
269 verifylist = [
270 ('description', identity_fakes.limit_description),
271 ('limit_id', identity_fakes.limit_id)
272 ]
273 parsed_args = self.check_parser(self.cmd, arglist, verifylist)
274
275 columns, data = self.cmd.take_action(parsed_args)
276
277 self.limit_mock.update.assert_called_with(
278 identity_fakes.limit_id,
279 description=identity_fakes.limit_description,
280 resource_limit=None
281 )
282
283 collist = (
284 'description', 'id', 'project_id', 'region_id', 'resource_limit',
285 'resource_name', 'service_id'
286 )
287 self.assertEqual(collist, columns)
288 datalist = (
289 identity_fakes.limit_description,
290 identity_fakes.limit_id,
291 identity_fakes.project_id,
292 None,
293 identity_fakes.limit_resource_limit,
294 identity_fakes.limit_resource_name,
295 identity_fakes.service_id
296 )
297 self.assertEqual(datalist, data)
298
299 def test_limit_set_resource_limit(self):
300 resource_limit = 20
301 limit = copy.deepcopy(identity_fakes.LIMIT)
302 limit['resource_limit'] = resource_limit
303 self.limit_mock.update.return_value = fakes.FakeResource(
304 None, limit, loaded=True
305 )
306
307 arglist = [
308 '--resource-limit', str(resource_limit),
309 identity_fakes.limit_id
310 ]
311 verifylist = [
312 ('resource_limit', resource_limit),
313 ('limit_id', identity_fakes.limit_id)
314 ]
315 parsed_args = self.check_parser(self.cmd, arglist, verifylist)
316
317 columns, data = self.cmd.take_action(parsed_args)
318
319 self.limit_mock.update.assert_called_with(
320 identity_fakes.limit_id,
321 description=None,
322 resource_limit=resource_limit
323 )
324
325 collist = (
326 'description', 'id', 'project_id', 'region_id', 'resource_limit',
327 'resource_name', 'service_id'
328 )
329 self.assertEqual(collist, columns)
330 datalist = (
331 None,
332 identity_fakes.limit_id,
333 identity_fakes.project_id,
334 None,
335 resource_limit,
336 identity_fakes.limit_resource_name,
337 identity_fakes.service_id
338 )
339 self.assertEqual(datalist, data)
340
341
342class TestLimitList(TestLimit):
343
344 def setUp(self):
345 super(TestLimitList, self).setUp()
346
347 self.limit_mock.list.return_value = [
348 fakes.FakeResource(
349 None,
350 copy.deepcopy(identity_fakes.LIMIT),
351 loaded=True
352 )
353 ]
354
355 self.cmd = limit.ListLimit(self.app, None)
356
357 def test_limit_list(self):
358 arglist = []
359 verifylist = []
360 parsed_args = self.check_parser(self.cmd, arglist, verifylist)
361
362 columns, data = self.cmd.take_action(parsed_args)
363
364 self.limit_mock.list.assert_called_with(
365 service=None, resource_name=None, region=None
366 )
367
368 collist = (
369 'ID', 'Project ID', 'Service ID', 'Resource Name',
370 'Resource Limit', 'Description', 'Region ID'
371 )
372 self.assertEqual(collist, columns)
373 datalist = ((
374 identity_fakes.limit_id,
375 identity_fakes.project_id,
376 identity_fakes.service_id,
377 identity_fakes.limit_resource_name,
378 identity_fakes.limit_resource_limit,
379 None,
380 None
381 ), )
382 self.assertEqual(datalist, tuple(data))
diff --git a/releasenotes/notes/bp-unified-limits-6c5fdb1c26805d86.yaml b/releasenotes/notes/bp-unified-limits-6c5fdb1c26805d86.yaml
new file mode 100644
index 0000000..b00de40
--- /dev/null
+++ b/releasenotes/notes/bp-unified-limits-6c5fdb1c26805d86.yaml
@@ -0,0 +1,7 @@
1---
2features:
3 - |
4 [`bp unified-limits <https://blueprints.launchpad.net/keystone/+spec/unified-limit>`_]
5 Support has been added for managing project-specific limits in keystone via
6 the ``limit`` command. Limits define limits of resources for projects to
7 consume once a limit has been registered.
diff --git a/setup.cfg b/setup.cfg
index f85d709..bfef10d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -268,6 +268,12 @@ openstack.identity.v3 =
268 implied_role_delete = openstackclient.identity.v3.implied_role:DeleteImpliedRole 268 implied_role_delete = openstackclient.identity.v3.implied_role:DeleteImpliedRole
269 implied_role_list = openstackclient.identity.v3.implied_role:ListImpliedRole 269 implied_role_list = openstackclient.identity.v3.implied_role:ListImpliedRole
270 270
271 limit_create = openstackclient.identity.v3.limit:CreateLimit
272 limit_delete = openstackclient.identity.v3.limit:DeleteLimit
273 limit_list = openstackclient.identity.v3.limit:ListLimit
274 limit_set = openstackclient.identity.v3.limit:SetLimit
275 limit_show = openstackclient.identity.v3.limit:ShowLimit
276
271 mapping_create = openstackclient.identity.v3.mapping:CreateMapping 277 mapping_create = openstackclient.identity.v3.mapping:CreateMapping
272 mapping_delete = openstackclient.identity.v3.mapping:DeleteMapping 278 mapping_delete = openstackclient.identity.v3.mapping:DeleteMapping
273 mapping_list = openstackclient.identity.v3.mapping:ListMapping 279 mapping_list = openstackclient.identity.v3.mapping:ListMapping