Browse Source

Merge "Implement support for registered limits"

tags/3.16.0
Zuul 10 months ago
parent
commit
d177727a6e

+ 140
- 0
doc/source/cli/command-objects/registered-limit.rst View File

@@ -0,0 +1,140 @@
1
+================
2
+registered limit
3
+================
4
+
5
+Identity v3
6
+
7
+Registered limits are used to define default limits for resources within a
8
+deployment.
9
+
10
+registered limit create
11
+-----------------------
12
+
13
+Create a new registered limit
14
+
15
+.. program:: registered limit create
16
+.. code:: bash
17
+
18
+    openstack registered limit create
19
+        [--description <description>]
20
+        [--region <region>]
21
+        --service <service>
22
+        --default-limit <default-limit>
23
+        <resource-name>
24
+
25
+.. option:: --description <description>
26
+
27
+   Useful description of the registered limit or its purpose
28
+
29
+.. option:: --region <region>
30
+
31
+   Region that the limit should be applied to
32
+
33
+.. describe:: --service  <service>
34
+
35
+   The service that is responsible for the resource being limited (required)
36
+
37
+.. describe:: --default-limit <default-limit>
38
+
39
+   The default limit for projects to assume unless explicitly overridden
40
+   (required)
41
+
42
+.. describe:: <resource-name>
43
+
44
+   The name of the resource to limit (e.g. cores or volumes)
45
+
46
+registered limit delete
47
+-----------------------
48
+
49
+Delete registered limit(s)
50
+
51
+.. program:: registered limit delete
52
+.. code:: bash
53
+
54
+    openstack registered limit delete
55
+        <registered-limit-id> [<registered-limit-id> ...]
56
+
57
+
58
+.. describe:: <registered-limit-id>
59
+
60
+    Registered limit(s) to delete (ID)
61
+
62
+registered limit list
63
+---------------------
64
+
65
+List registered limits
66
+
67
+.. program:: registered limit list
68
+.. code:: bash
69
+
70
+    openstack registered limit list
71
+        [--service <service>]
72
+        [--resource-name <resource-name>]
73
+        [--region <region>]
74
+
75
+.. option:: --service <service>
76
+
77
+    The service to filter the response by (name or ID)
78
+
79
+.. option:: --resource-name <resource-name>
80
+
81
+    The name of the resource to filter the response by
82
+
83
+.. option:: --region <region>
84
+
85
+   The region name to filter the response by
86
+
87
+registered limit show
88
+---------------------
89
+
90
+Display details about a registered limit
91
+
92
+.. program:: registered limit show
93
+.. code:: bash
94
+
95
+    openstack registered limit show
96
+        <registered-limit-id>
97
+
98
+.. describe:: <registered-limit-id>
99
+
100
+    Registered limit to display (ID)
101
+
102
+registered limit set
103
+--------------------
104
+
105
+Update a registered limit
106
+
107
+.. program:: registered limit set
108
+.. code:: bash
109
+
110
+    openstack registered limit set
111
+        [--service <service>]
112
+        [--resource-name <resource-name>]
113
+        [--default-limit <default-limit>]
114
+        [--description <description>]
115
+        [--region <region>]
116
+        <registered-limit-id>
117
+
118
+.. option:: --service <service>
119
+
120
+    The service of the registered limit to update (ID or name)
121
+
122
+.. option:: --resource-name <resource-name>
123
+
124
+    The name of the resource for the limit
125
+
126
+.. option:: --default-limit <default-limit>
127
+
128
+    The default limit for projects to assume for a given resource
129
+
130
+.. option:: --description <description>
131
+
132
+    A useful description of the limit or its purpose
133
+
134
+.. option:: --region <region>
135
+
136
+    The region the limit should apply to
137
+
138
+.. describe:: <registered-limit-id>
139
+
140
+    Registered limit to display (ID)

+ 1
- 1
lower-constraints.txt View File

@@ -95,7 +95,7 @@ python-heatclient==1.10.0
95 95
 python-ironic-inspector-client==1.5.0
96 96
 python-ironicclient==2.3.0
97 97
 python-karborclient==0.6.0
98
-python-keystoneclient==3.15.0
98
+python-keystoneclient==3.17.0
99 99
 python-mimeparse==1.6.0
100 100
 python-mistralclient==3.1.0
101 101
 python-muranoclient==0.8.2

+ 260
- 0
openstackclient/identity/v3/registered_limit.py View File

@@ -0,0 +1,260 @@
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
+"""Registered limits action implementations."""
15
+
16
+import logging
17
+
18
+from osc_lib.command import command
19
+from osc_lib import exceptions
20
+from osc_lib import utils
21
+import six
22
+
23
+from openstackclient.i18n import _
24
+from openstackclient.identity import common as common_utils
25
+
26
+LOG = logging.getLogger(__name__)
27
+
28
+
29
+class CreateRegisteredLimit(command.ShowOne):
30
+    _description = _("Create a registered limit")
31
+
32
+    def get_parser(self, prog_name):
33
+        parser = super(CreateRegisteredLimit, self).get_parser(prog_name)
34
+        parser.add_argument(
35
+            '--description',
36
+            metavar='<description>',
37
+            help=_('Description of the registered limit'),
38
+        )
39
+        parser.add_argument(
40
+            '--region',
41
+            metavar='<region>',
42
+            help=_('Region for the registered limit to affect'),
43
+        )
44
+        parser.add_argument(
45
+            '--service',
46
+            metavar='<service>',
47
+            required=True,
48
+            help=_('Service responsible for the resource to limit (required)'),
49
+        )
50
+        parser.add_argument(
51
+            '--default-limit',
52
+            type=int,
53
+            metavar='<default-limit>',
54
+            required=True,
55
+            help=_('The default limit for the resources to assume (required)'),
56
+        )
57
+        parser.add_argument(
58
+            'resource_name',
59
+            metavar='<resource-name>',
60
+            help=_('The name of the resource to limit'),
61
+        )
62
+        return parser
63
+
64
+    def take_action(self, parsed_args):
65
+        identity_client = self.app.client_manager.identity
66
+
67
+        service = utils.find_resource(
68
+            identity_client.services, parsed_args.service
69
+        )
70
+        region = None
71
+        if parsed_args.region:
72
+            region = utils.find_resource(
73
+                identity_client.regions, parsed_args.region
74
+            )
75
+
76
+        registered_limit = identity_client.registered_limits.create(
77
+            service,
78
+            parsed_args.resource_name,
79
+            parsed_args.default_limit,
80
+            description=parsed_args.description,
81
+            region=region
82
+        )
83
+
84
+        registered_limit._info.pop('links', None)
85
+        return zip(*sorted(six.iteritems(registered_limit._info)))
86
+
87
+
88
+class DeleteRegisteredLimit(command.Command):
89
+    _description = _("Delete a registered limit")
90
+
91
+    def get_parser(self, prog_name):
92
+        parser = super(DeleteRegisteredLimit, self).get_parser(prog_name)
93
+        parser.add_argument(
94
+            'registered_limit_id',
95
+            metavar='<registered-limit-id>',
96
+            nargs="+",
97
+            help=_('Registered limit to delete (ID)'),
98
+        )
99
+        return parser
100
+
101
+    def take_action(self, parsed_args):
102
+        identity_client = self.app.client_manager.identity
103
+
104
+        errors = 0
105
+        for registered_limit_id in parsed_args.registered_limit_id:
106
+            try:
107
+                identity_client.registered_limits.delete(registered_limit_id)
108
+            except Exception as e:
109
+                errors += 1
110
+                from pprint import pprint
111
+                pprint(type(e))
112
+                LOG.error(_("Failed to delete registered limit with ID "
113
+                            "'%(id)s': %(e)s"),
114
+                          {'id': registered_limit_id, 'e': e})
115
+
116
+        if errors > 0:
117
+            total = len(parsed_args.registered_limit_id)
118
+            msg = (_("%(errors)s of %(total)s registered limits failed to "
119
+                   "delete.") % {'errors': errors, 'total': total})
120
+            raise exceptions.CommandError(msg)
121
+
122
+
123
+class ListRegisteredLimit(command.Lister):
124
+    _description = _("List registered limits")
125
+
126
+    def get_parser(self, prog_name):
127
+        parser = super(ListRegisteredLimit, self).get_parser(prog_name)
128
+        parser.add_argument(
129
+            '--service',
130
+            metavar='<service>',
131
+            help=_('Service responsible for the resource to limit'),
132
+        )
133
+        parser.add_argument(
134
+            '--resource-name',
135
+            metavar='<resource-name>',
136
+            dest='resource_name',
137
+            help=_('The name of the resource to limit'),
138
+        )
139
+        parser.add_argument(
140
+            '--region',
141
+            metavar='<region>',
142
+            help=_('Region for the limit to affect.'),
143
+        )
144
+        return parser
145
+
146
+    def take_action(self, parsed_args):
147
+        identity_client = self.app.client_manager.identity
148
+
149
+        service = None
150
+        if parsed_args.service:
151
+            service = common_utils.find_service(
152
+                identity_client, parsed_args.service
153
+            )
154
+        region = None
155
+        if parsed_args.region:
156
+            region = utils.find_resource(
157
+                identity_client.regions, parsed_args.region
158
+            )
159
+
160
+        registered_limits = identity_client.registered_limits.list(
161
+            service=service,
162
+            resource_name=parsed_args.resource_name,
163
+            region=region
164
+        )
165
+
166
+        columns = (
167
+            'ID', 'Service ID', 'Resource Name', 'Default Limit',
168
+            'Description', 'Region ID'
169
+        )
170
+        return (
171
+            columns,
172
+            (utils.get_item_properties(s, columns) for s in registered_limits),
173
+        )
174
+
175
+
176
+class SetRegisteredLimit(command.ShowOne):
177
+    _description = _("Update information about a registered limit")
178
+
179
+    def get_parser(self, prog_name):
180
+        parser = super(SetRegisteredLimit, self).get_parser(prog_name)
181
+        parser.add_argument(
182
+            'registered_limit_id',
183
+            metavar='<registered-limit-id>',
184
+            help=_('Registered limit to update (ID)'),
185
+        )
186
+        parser.add_argument(
187
+            '--service',
188
+            metavar='<service>',
189
+            help=_('Service responsible for the resource to limit'),
190
+        )
191
+        parser.add_argument(
192
+            '--resource-name',
193
+            metavar='<resource-name>',
194
+            help=_('The name of the resource to limit'),
195
+        )
196
+        parser.add_argument(
197
+            '--default-limit',
198
+            metavar='<default-limit>',
199
+            type=int,
200
+            help=_('The default limit for the resources to assume'),
201
+        )
202
+        parser.add_argument(
203
+            '--description',
204
+            metavar='<description>',
205
+            help=_('Description of the registered limit'),
206
+        )
207
+        parser.add_argument(
208
+            '--region',
209
+            metavar='<region>',
210
+            help=_('Region for the registered limit to affect.'),
211
+        )
212
+        return parser
213
+
214
+    def take_action(self, parsed_args):
215
+        identity_client = self.app.client_manager.identity
216
+
217
+        service = None
218
+        if parsed_args.service:
219
+            service = common_utils.find_service(
220
+                identity_client, parsed_args.service
221
+            )
222
+
223
+        region = None
224
+        if parsed_args.region:
225
+            region = utils.find_resource(
226
+                identity_client.regions, parsed_args.region
227
+            )
228
+
229
+        registered_limit = identity_client.registered_limits.update(
230
+            parsed_args.registered_limit_id,
231
+            service=service,
232
+            resource_name=parsed_args.resource_name,
233
+            default_limit=parsed_args.default_limit,
234
+            description=parsed_args.description,
235
+            region=region
236
+        )
237
+
238
+        registered_limit._info.pop('links', None)
239
+        return zip(*sorted(six.iteritems(registered_limit._info)))
240
+
241
+
242
+class ShowRegisteredLimit(command.ShowOne):
243
+    _description = _("Display registered limit details")
244
+
245
+    def get_parser(self, prog_name):
246
+        parser = super(ShowRegisteredLimit, self).get_parser(prog_name)
247
+        parser.add_argument(
248
+            'registered_limit_id',
249
+            metavar='<registered-limit-id>',
250
+            help=_('Registered limit to display (ID)'),
251
+        )
252
+        return parser
253
+
254
+    def take_action(self, parsed_args):
255
+        identity_client = self.app.client_manager.identity
256
+        registered_limit = identity_client.registered_limits.get(
257
+            parsed_args.registered_limit_id
258
+        )
259
+        registered_limit._info.pop('links', None)
260
+        return zip(*sorted(six.iteritems(registered_limit._info)))

+ 37
- 0
openstackclient/tests/functional/identity/v3/common.py View File

@@ -54,6 +54,11 @@ class IdentityTests(base.TestCase):
54 54
                                      'Auth URL']
55 55
     IMPLIED_ROLE_LIST_HEADERS = ['Prior Role ID', 'Prior Role Name',
56 56
                                  'Implied Role ID', 'Implied Role Name']
57
+    REGISTERED_LIMIT_FIELDS = ['id', 'service_id', 'resource_name',
58
+                               'default_limit', 'description', 'region_id']
59
+    REGISTERED_LIMIT_LIST_HEADERS = ['ID', 'Service ID', 'Resource Name',
60
+                                     'Default Limit', 'Description',
61
+                                     'Region ID']
57 62
 
58 63
     @classmethod
59 64
     def setUpClass(cls):
@@ -319,3 +324,35 @@ class IdentityTests(base.TestCase):
319 324
         items = self.parse_show(raw_output)
320 325
         self.assert_show_fields(items, self.SERVICE_PROVIDER_FIELDS)
321 326
         return service_provider
327
+
328
+    def _create_dummy_registered_limit(self, add_clean_up=True):
329
+        service_name = self._create_dummy_service()
330
+        resource_name = data_utils.rand_name('resource_name')
331
+        params = {
332
+            'service_name': service_name,
333
+            'default_limit': 10,
334
+            'resource_name': resource_name
335
+        }
336
+        raw_output = self.openstack(
337
+            'registered limit create'
338
+            ' --service %(service_name)s'
339
+            ' --default-limit %(default_limit)s'
340
+            ' %(resource_name)s' % params
341
+        )
342
+        items = self.parse_show(raw_output)
343
+        registered_limit_id = self._extract_value_from_items('id', items)
344
+
345
+        if add_clean_up:
346
+            self.addCleanup(
347
+                self.openstack,
348
+                'registered limit delete %s' % registered_limit_id
349
+            )
350
+
351
+        self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
352
+        return registered_limit_id
353
+
354
+    def _extract_value_from_items(self, key, items):
355
+        for d in items:
356
+            for k, v in d.iteritems():
357
+                if k == key:
358
+                    return v

+ 184
- 0
openstackclient/tests/functional/identity/v3/test_registered_limit.py View File

@@ -0,0 +1,184 @@
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
+from tempest.lib.common.utils import data_utils
14
+
15
+from openstackclient.tests.functional.identity.v3 import common
16
+
17
+
18
+class RegisteredLimitTestCase(common.IdentityTests):
19
+
20
+    def test_registered_limit_create_with_service_name(self):
21
+        self._create_dummy_registered_limit()
22
+
23
+    def test_registered_limit_create_with_service_id(self):
24
+        service_name = self._create_dummy_service()
25
+        raw_output = self.openstack(
26
+            'service show'
27
+            ' %(service_name)s' % {'service_name': service_name}
28
+        )
29
+        service_items = self.parse_show(raw_output)
30
+        service_id = self._extract_value_from_items('id', service_items)
31
+
32
+        raw_output = self.openstack(
33
+            'registered limit create'
34
+            ' --service %(service_id)s'
35
+            ' --default-limit %(default_limit)s'
36
+            ' %(resource_name)s' % {
37
+                'service_id': service_id,
38
+                'default_limit': 10,
39
+                'resource_name': 'cores'
40
+            }
41
+        )
42
+        items = self.parse_show(raw_output)
43
+        registered_limit_id = self._extract_value_from_items('id', items)
44
+        self.addCleanup(
45
+            self.openstack,
46
+            'registered limit delete'
47
+            ' %(registered_limit_id)s' % {
48
+                'registered_limit_id': registered_limit_id
49
+            }
50
+        )
51
+
52
+        self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
53
+
54
+    def test_registered_limit_create_with_options(self):
55
+        service_name = self._create_dummy_service()
56
+        region_id = self._create_dummy_region()
57
+        params = {
58
+            'service_name': service_name,
59
+            'resource_name': 'cores',
60
+            'default_limit': 10,
61
+            'description': 'default limit for cores',
62
+            'region_id': region_id
63
+        }
64
+
65
+        raw_output = self.openstack(
66
+            'registered limit create'
67
+            ' --description \'%(description)s\''
68
+            ' --region %(region_id)s'
69
+            ' --service %(service_name)s'
70
+            ' --default-limit %(default_limit)s'
71
+            ' %(resource_name)s' % params
72
+        )
73
+        items = self.parse_show(raw_output)
74
+        registered_limit_id = self._extract_value_from_items('id', items)
75
+        self.addCleanup(
76
+            self.openstack,
77
+            'registered limit delete %(registered_limit_id)s' % {
78
+                'registered_limit_id': registered_limit_id
79
+            }
80
+        )
81
+
82
+        self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
83
+
84
+    def test_registered_limit_show(self):
85
+        registered_limit_id = self._create_dummy_registered_limit()
86
+        raw_output = self.openstack(
87
+            'registered limit show %(registered_limit_id)s' % {
88
+                'registered_limit_id': registered_limit_id
89
+            }
90
+        )
91
+        items = self.parse_show(raw_output)
92
+        self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
93
+
94
+    def test_registered_limit_set_region_id(self):
95
+        region_id = self._create_dummy_region()
96
+        registered_limit_id = self._create_dummy_registered_limit()
97
+
98
+        params = {
99
+            'registered_limit_id': registered_limit_id,
100
+            'region_id': region_id
101
+        }
102
+        raw_output = self.openstack(
103
+            'registered limit set'
104
+            ' %(registered_limit_id)s'
105
+            ' --region %(region_id)s' % params
106
+        )
107
+        items = self.parse_show(raw_output)
108
+        self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
109
+
110
+    def test_registered_limit_set_description(self):
111
+        registered_limit_id = self._create_dummy_registered_limit()
112
+        params = {
113
+            'registered_limit_id': registered_limit_id,
114
+            'description': 'updated description'
115
+        }
116
+        raw_output = self.openstack(
117
+            'registered limit set'
118
+            ' %(registered_limit_id)s'
119
+            ' --description \'%(description)s\'' % params
120
+        )
121
+        items = self.parse_show(raw_output)
122
+        self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
123
+
124
+    def test_registered_limit_set_service(self):
125
+        registered_limit_id = self._create_dummy_registered_limit()
126
+        service_name = self._create_dummy_service()
127
+        params = {
128
+            'registered_limit_id': registered_limit_id,
129
+            'service': service_name
130
+        }
131
+        raw_output = self.openstack(
132
+            'registered limit set'
133
+            ' %(registered_limit_id)s'
134
+            ' --service %(service)s' % params
135
+        )
136
+        items = self.parse_show(raw_output)
137
+        self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
138
+
139
+    def test_registered_limit_set_default_limit(self):
140
+        registered_limit_id = self._create_dummy_registered_limit()
141
+        params = {
142
+            'registered_limit_id': registered_limit_id,
143
+            'default_limit': 20
144
+        }
145
+        raw_output = self.openstack(
146
+            'registered limit set'
147
+            ' %(registered_limit_id)s'
148
+            ' --default-limit %(default_limit)s' % params
149
+        )
150
+        items = self.parse_show(raw_output)
151
+        self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
152
+
153
+    def test_registered_limit_set_resource_name(self):
154
+        registered_limit_id = self._create_dummy_registered_limit()
155
+        resource_name = data_utils.rand_name('resource_name')
156
+        params = {
157
+            'registered_limit_id': registered_limit_id,
158
+            'resource_name': resource_name
159
+        }
160
+        raw_output = self.openstack(
161
+            'registered limit set'
162
+            ' %(registered_limit_id)s'
163
+            ' --resource-name %(resource_name)s' % params
164
+        )
165
+        items = self.parse_show(raw_output)
166
+        self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
167
+
168
+    def test_registered_limit_list(self):
169
+        self._create_dummy_registered_limit()
170
+        raw_output = self.openstack('registered limit list')
171
+        items = self.parse_listing(raw_output)
172
+        self.assert_table_structure(items, self.REGISTERED_LIMIT_LIST_HEADERS)
173
+
174
+    def test_registered_limit_delete(self):
175
+        registered_limit_id = self._create_dummy_registered_limit(
176
+            add_clean_up=False
177
+        )
178
+        raw_output = self.openstack(
179
+            'registered limit delete'
180
+            ' %(registered_limit_id)s' % {
181
+                'registered_limit_id': registered_limit_id
182
+            }
183
+        )
184
+        self.assertEqual(0, len(raw_output))

+ 23
- 0
openstackclient/tests/unit/identity/v3/fakes.py View File

@@ -486,6 +486,27 @@ APP_CRED_OPTIONS = {
486 486
     'secret': app_cred_secret
487 487
 }
488 488
 
489
+registered_limit_id = 'registered-limit-id'
490
+registered_limit_default_limit = 10
491
+registered_limit_description = 'default limit of foobars'
492
+registered_limit_resource_name = 'foobars'
493
+REGISTERED_LIMIT = {
494
+    'id': registered_limit_id,
495
+    'default_limit': registered_limit_default_limit,
496
+    'resource_name': registered_limit_resource_name,
497
+    'service_id': service_id,
498
+    'description': None,
499
+    'region_id': None
500
+}
501
+REGISTERED_LIMIT_OPTIONS = {
502
+    'id': registered_limit_id,
503
+    'default_limit': registered_limit_default_limit,
504
+    'resource_name': registered_limit_resource_name,
505
+    'service_id': service_id,
506
+    'description': registered_limit_description,
507
+    'region_id': region_id
508
+}
509
+
489 510
 
490 511
 def fake_auth_ref(fake_token, fake_service=None):
491 512
     """Create an auth_ref using keystoneauth's fixtures"""
@@ -578,6 +599,8 @@ class FakeIdentityv3Client(object):
578 599
                                                                          {})
579 600
         self.inference_rules = mock.Mock()
580 601
         self.inference_rules.resource_class = fakes.FakeResource(None, {})
602
+        self.registered_limits = mock.Mock()
603
+        self.registered_limits.resource_class = fakes.FakeResource(None, {})
581 604
 
582 605
 
583 606
 class FakeFederationManager(object):

+ 510
- 0
openstackclient/tests/unit/identity/v3/test_registered_limit.py View File

@@ -0,0 +1,510 @@
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
+import copy
14
+
15
+from keystoneauth1.exceptions import http as ksa_exceptions
16
+from osc_lib import exceptions
17
+
18
+from openstackclient.identity.v3 import registered_limit
19
+from openstackclient.tests.unit import fakes
20
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
21
+
22
+
23
+class TestRegisteredLimit(identity_fakes.TestIdentityv3):
24
+
25
+    def setUp(self):
26
+        super(TestRegisteredLimit, self).setUp()
27
+
28
+        identity_manager = self.app.client_manager.identity
29
+        self.registered_limit_mock = identity_manager.registered_limits
30
+
31
+        self.services_mock = identity_manager.services
32
+        self.services_mock.reset_mock()
33
+
34
+        self.regions_mock = identity_manager.regions
35
+        self.regions_mock.reset_mock()
36
+
37
+
38
+class TestRegisteredLimitCreate(TestRegisteredLimit):
39
+
40
+    def setUp(self):
41
+        super(TestRegisteredLimitCreate, self).setUp()
42
+
43
+        self.service = fakes.FakeResource(
44
+            None,
45
+            copy.deepcopy(identity_fakes.SERVICE),
46
+            loaded=True
47
+        )
48
+        self.services_mock.get.return_value = self.service
49
+
50
+        self.region = fakes.FakeResource(
51
+            None,
52
+            copy.deepcopy(identity_fakes.REGION),
53
+            loaded=True
54
+        )
55
+        self.regions_mock.get.return_value = self.region
56
+
57
+        self.cmd = registered_limit.CreateRegisteredLimit(self.app, None)
58
+
59
+    def test_registered_limit_create_without_options(self):
60
+        self.registered_limit_mock.create.return_value = fakes.FakeResource(
61
+            None,
62
+            copy.deepcopy(identity_fakes.REGISTERED_LIMIT),
63
+            loaded=True
64
+        )
65
+
66
+        resource_name = identity_fakes.registered_limit_resource_name
67
+        default_limit = identity_fakes.registered_limit_default_limit
68
+        arglist = [
69
+            '--service', identity_fakes.service_id,
70
+            '--default-limit', '10',
71
+            resource_name,
72
+        ]
73
+
74
+        verifylist = [
75
+            ('service', identity_fakes.service_id),
76
+            ('default_limit', default_limit),
77
+            ('resource_name', resource_name)
78
+        ]
79
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
80
+
81
+        columns, data = self.cmd.take_action(parsed_args)
82
+
83
+        kwargs = {'description': None, 'region': None}
84
+        self.registered_limit_mock.create.assert_called_with(
85
+            self.service, resource_name, default_limit, **kwargs
86
+        )
87
+
88
+        collist = ('default_limit', 'description', 'id', 'region_id',
89
+                   'resource_name', 'service_id')
90
+
91
+        self.assertEqual(collist, columns)
92
+        datalist = (
93
+            identity_fakes.registered_limit_default_limit,
94
+            None,
95
+            identity_fakes.registered_limit_id,
96
+            None,
97
+            identity_fakes.registered_limit_resource_name,
98
+            identity_fakes.service_id
99
+        )
100
+        self.assertEqual(datalist, data)
101
+
102
+    def test_registered_limit_create_with_options(self):
103
+        self.registered_limit_mock.create.return_value = fakes.FakeResource(
104
+            None,
105
+            copy.deepcopy(identity_fakes.REGISTERED_LIMIT_OPTIONS),
106
+            loaded=True
107
+        )
108
+
109
+        resource_name = identity_fakes.registered_limit_resource_name
110
+        default_limit = identity_fakes.registered_limit_default_limit
111
+        description = identity_fakes.registered_limit_description
112
+        arglist = [
113
+            '--region', identity_fakes.region_id,
114
+            '--description', description,
115
+            '--service', identity_fakes.service_id,
116
+            '--default-limit', '10',
117
+            resource_name
118
+        ]
119
+
120
+        verifylist = [
121
+            ('region', identity_fakes.region_id),
122
+            ('description', description),
123
+            ('service', identity_fakes.service_id),
124
+            ('default_limit', default_limit),
125
+            ('resource_name', resource_name)
126
+        ]
127
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
128
+
129
+        columns, data = self.cmd.take_action(parsed_args)
130
+
131
+        kwargs = {'description': description, 'region': self.region}
132
+        self.registered_limit_mock.create.assert_called_with(
133
+            self.service, resource_name, default_limit, **kwargs
134
+        )
135
+
136
+        collist = ('default_limit', 'description', 'id', 'region_id',
137
+                   'resource_name', 'service_id')
138
+
139
+        self.assertEqual(collist, columns)
140
+        datalist = (
141
+            identity_fakes.registered_limit_default_limit,
142
+            description,
143
+            identity_fakes.registered_limit_id,
144
+            identity_fakes.region_id,
145
+            identity_fakes.registered_limit_resource_name,
146
+            identity_fakes.service_id
147
+        )
148
+        self.assertEqual(datalist, data)
149
+
150
+
151
+class TestRegisteredLimitDelete(TestRegisteredLimit):
152
+
153
+    def setUp(self):
154
+        super(TestRegisteredLimitDelete, self).setUp()
155
+
156
+        self.cmd = registered_limit.DeleteRegisteredLimit(self.app, None)
157
+
158
+    def test_registered_limit_delete(self):
159
+        self.registered_limit_mock.delete.return_value = None
160
+
161
+        arglist = [identity_fakes.registered_limit_id]
162
+        verifylist = [
163
+            ('registered_limit_id', [identity_fakes.registered_limit_id])
164
+        ]
165
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
166
+
167
+        result = self.cmd.take_action(parsed_args)
168
+
169
+        self.registered_limit_mock.delete.assert_called_with(
170
+            identity_fakes.registered_limit_id
171
+        )
172
+        self.assertIsNone(result)
173
+
174
+    def test_registered_limit_delete_with_exception(self):
175
+        return_value = ksa_exceptions.NotFound()
176
+        self.registered_limit_mock.delete.side_effect = return_value
177
+
178
+        arglist = ['fake-registered-limit-id']
179
+        verifylist = [
180
+            ('registered_limit_id', ['fake-registered-limit-id'])
181
+        ]
182
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
183
+
184
+        try:
185
+            self.cmd.take_action(parsed_args)
186
+            self.fail('CommandError should be raised.')
187
+        except exceptions.CommandError as e:
188
+            self.assertEqual(
189
+                '1 of 1 registered limits failed to delete.', str(e)
190
+            )
191
+
192
+
193
+class TestRegisteredLimitShow(TestRegisteredLimit):
194
+
195
+    def setUp(self):
196
+        super(TestRegisteredLimitShow, self).setUp()
197
+
198
+        self.registered_limit_mock.get.return_value = fakes.FakeResource(
199
+            None,
200
+            copy.deepcopy(identity_fakes.REGISTERED_LIMIT),
201
+            loaded=True
202
+        )
203
+
204
+        self.cmd = registered_limit.ShowRegisteredLimit(self.app, None)
205
+
206
+    def test_registered_limit_show(self):
207
+        arglist = [identity_fakes.registered_limit_id]
208
+        verifylist = [
209
+            ('registered_limit_id', identity_fakes.registered_limit_id)
210
+        ]
211
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
212
+
213
+        columns, data = self.cmd.take_action(parsed_args)
214
+
215
+        self.registered_limit_mock.get.assert_called_with(
216
+            identity_fakes.registered_limit_id
217
+        )
218
+
219
+        collist = (
220
+            'default_limit', 'description', 'id', 'region_id', 'resource_name',
221
+            'service_id'
222
+        )
223
+        self.assertEqual(collist, columns)
224
+        datalist = (
225
+            identity_fakes.registered_limit_default_limit,
226
+            None,
227
+            identity_fakes.registered_limit_id,
228
+            None,
229
+            identity_fakes.registered_limit_resource_name,
230
+            identity_fakes.service_id
231
+        )
232
+        self.assertEqual(datalist, data)
233
+
234
+
235
+class TestRegisteredLimitSet(TestRegisteredLimit):
236
+
237
+    def setUp(self):
238
+        super(TestRegisteredLimitSet, self).setUp()
239
+        self.cmd = registered_limit.SetRegisteredLimit(self.app, None)
240
+
241
+    def test_registered_limit_set_description(self):
242
+        registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT)
243
+        registered_limit['description'] = (
244
+            identity_fakes.registered_limit_description
245
+        )
246
+        self.registered_limit_mock.update.return_value = fakes.FakeResource(
247
+            None, registered_limit, loaded=True
248
+        )
249
+
250
+        arglist = [
251
+            '--description', identity_fakes.registered_limit_description,
252
+            identity_fakes.registered_limit_id
253
+        ]
254
+        verifylist = [
255
+            ('description', identity_fakes.registered_limit_description),
256
+            ('registered_limit_id', identity_fakes.registered_limit_id)
257
+        ]
258
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
259
+
260
+        columns, data = self.cmd.take_action(parsed_args)
261
+
262
+        self.registered_limit_mock.update.assert_called_with(
263
+            identity_fakes.registered_limit_id,
264
+            service=None,
265
+            resource_name=None,
266
+            default_limit=None,
267
+            description=identity_fakes.registered_limit_description,
268
+            region=None
269
+        )
270
+
271
+        collist = (
272
+            'default_limit', 'description', 'id', 'region_id', 'resource_name',
273
+            'service_id'
274
+        )
275
+        self.assertEqual(collist, columns)
276
+        datalist = (
277
+            identity_fakes.registered_limit_default_limit,
278
+            identity_fakes.registered_limit_description,
279
+            identity_fakes.registered_limit_id,
280
+            None,
281
+            identity_fakes.registered_limit_resource_name,
282
+            identity_fakes.service_id
283
+        )
284
+        self.assertEqual(datalist, data)
285
+
286
+    def test_registered_limit_set_default_limit(self):
287
+        registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT)
288
+        default_limit = 20
289
+        registered_limit['default_limit'] = default_limit
290
+        self.registered_limit_mock.update.return_value = fakes.FakeResource(
291
+            None, registered_limit, loaded=True
292
+        )
293
+
294
+        arglist = [
295
+            '--default-limit', str(default_limit),
296
+            identity_fakes.registered_limit_id
297
+        ]
298
+        verifylist = [
299
+            ('default_limit', default_limit),
300
+            ('registered_limit_id', identity_fakes.registered_limit_id)
301
+        ]
302
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
303
+
304
+        columns, data = self.cmd.take_action(parsed_args)
305
+
306
+        self.registered_limit_mock.update.assert_called_with(
307
+            identity_fakes.registered_limit_id,
308
+            service=None,
309
+            resource_name=None,
310
+            default_limit=default_limit,
311
+            description=None,
312
+            region=None
313
+        )
314
+
315
+        collist = (
316
+            'default_limit', 'description', 'id', 'region_id', 'resource_name',
317
+            'service_id'
318
+        )
319
+        self.assertEqual(collist, columns)
320
+        datalist = (
321
+            default_limit,
322
+            None,
323
+            identity_fakes.registered_limit_id,
324
+            None,
325
+            identity_fakes.registered_limit_resource_name,
326
+            identity_fakes.service_id
327
+        )
328
+        self.assertEqual(datalist, data)
329
+
330
+    def test_registered_limit_set_resource_name(self):
331
+        registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT)
332
+        resource_name = 'volumes'
333
+        registered_limit['resource_name'] = resource_name
334
+        self.registered_limit_mock.update.return_value = fakes.FakeResource(
335
+            None, registered_limit, loaded=True
336
+        )
337
+
338
+        arglist = [
339
+            '--resource-name', resource_name,
340
+            identity_fakes.registered_limit_id
341
+        ]
342
+        verifylist = [
343
+            ('resource_name', resource_name),
344
+            ('registered_limit_id', identity_fakes.registered_limit_id)
345
+        ]
346
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
347
+
348
+        columns, data = self.cmd.take_action(parsed_args)
349
+
350
+        self.registered_limit_mock.update.assert_called_with(
351
+            identity_fakes.registered_limit_id,
352
+            service=None,
353
+            resource_name=resource_name,
354
+            default_limit=None,
355
+            description=None,
356
+            region=None
357
+        )
358
+
359
+        collist = (
360
+            'default_limit', 'description', 'id', 'region_id', 'resource_name',
361
+            'service_id'
362
+        )
363
+        self.assertEqual(collist, columns)
364
+        datalist = (
365
+            identity_fakes.registered_limit_default_limit,
366
+            None,
367
+            identity_fakes.registered_limit_id,
368
+            None,
369
+            resource_name,
370
+            identity_fakes.service_id
371
+        )
372
+        self.assertEqual(datalist, data)
373
+
374
+    def test_registered_limit_set_service(self):
375
+        registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT)
376
+        service = identity_fakes.FakeService.create_one_service()
377
+        registered_limit['service_id'] = service.id
378
+        self.registered_limit_mock.update.return_value = fakes.FakeResource(
379
+            None, registered_limit, loaded=True
380
+        )
381
+        self.services_mock.get.return_value = service
382
+
383
+        arglist = [
384
+            '--service', service.id,
385
+            identity_fakes.registered_limit_id
386
+        ]
387
+        verifylist = [
388
+            ('service', service.id),
389
+            ('registered_limit_id', identity_fakes.registered_limit_id)
390
+        ]
391
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
392
+
393
+        columns, data = self.cmd.take_action(parsed_args)
394
+
395
+        self.registered_limit_mock.update.assert_called_with(
396
+            identity_fakes.registered_limit_id,
397
+            service=service,
398
+            resource_name=None,
399
+            default_limit=None,
400
+            description=None,
401
+            region=None
402
+        )
403
+
404
+        collist = (
405
+            'default_limit', 'description', 'id', 'region_id', 'resource_name',
406
+            'service_id'
407
+        )
408
+        self.assertEqual(collist, columns)
409
+        datalist = (
410
+            identity_fakes.registered_limit_default_limit,
411
+            None,
412
+            identity_fakes.registered_limit_id,
413
+            None,
414
+            identity_fakes.registered_limit_resource_name,
415
+            service.id
416
+        )
417
+        self.assertEqual(datalist, data)
418
+
419
+    def test_registered_limit_set_region(self):
420
+        registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT)
421
+        region = identity_fakes.REGION
422
+        region['id'] = 'RegionTwo'
423
+        region = fakes.FakeResource(
424
+            None,
425
+            copy.deepcopy(region),
426
+            loaded=True
427
+        )
428
+        registered_limit['region_id'] = region.id
429
+        self.registered_limit_mock.update.return_value = fakes.FakeResource(
430
+            None, registered_limit, loaded=True
431
+        )
432
+        self.regions_mock.get.return_value = region
433
+
434
+        arglist = [
435
+            '--region', region.id,
436
+            identity_fakes.registered_limit_id
437
+        ]
438
+        verifylist = [
439
+            ('region', region.id),
440
+            ('registered_limit_id', identity_fakes.registered_limit_id)
441
+        ]
442
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
443
+
444
+        columns, data = self.cmd.take_action(parsed_args)
445
+
446
+        self.registered_limit_mock.update.assert_called_with(
447
+            identity_fakes.registered_limit_id,
448
+            service=None,
449
+            resource_name=None,
450
+            default_limit=None,
451
+            description=None,
452
+            region=region
453
+        )
454
+
455
+        collist = (
456
+            'default_limit', 'description', 'id', 'region_id', 'resource_name',
457
+            'service_id'
458
+        )
459
+        self.assertEqual(collist, columns)
460
+        datalist = (
461
+            identity_fakes.registered_limit_default_limit,
462
+            None,
463
+            identity_fakes.registered_limit_id,
464
+            region.id,
465
+            identity_fakes.registered_limit_resource_name,
466
+            identity_fakes.service_id
467
+        )
468
+        self.assertEqual(datalist, data)
469
+
470
+
471
+class TestRegisteredLimitList(TestRegisteredLimit):
472
+
473
+    def setUp(self):
474
+        super(TestRegisteredLimitList, self).setUp()
475
+
476
+        self.registered_limit_mock.get.return_value = fakes.FakeResource(
477
+            None,
478
+            copy.deepcopy(identity_fakes.REGISTERED_LIMIT),
479
+            loaded=True
480
+        )
481
+
482
+        self.cmd = registered_limit.ShowRegisteredLimit(self.app, None)
483
+
484
+    def test_limit_show(self):
485
+        arglist = [identity_fakes.registered_limit_id]
486
+        verifylist = [
487
+            ('registered_limit_id', identity_fakes.registered_limit_id)
488
+        ]
489
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
490
+
491
+        columns, data = self.cmd.take_action(parsed_args)
492
+
493
+        self.registered_limit_mock.get.assert_called_with(
494
+            identity_fakes.registered_limit_id
495
+        )
496
+
497
+        collist = (
498
+            'default_limit', 'description', 'id', 'region_id', 'resource_name',
499
+            'service_id'
500
+        )
501
+        self.assertEqual(collist, columns)
502
+        datalist = (
503
+            identity_fakes.registered_limit_default_limit,
504
+            None,
505
+            identity_fakes.registered_limit_id,
506
+            None,
507
+            identity_fakes.registered_limit_resource_name,
508
+            identity_fakes.service_id
509
+        )
510
+        self.assertEqual(datalist, data)

+ 7
- 0
releasenotes/notes/bp-unified-limits-58f166401534a4ff.yaml View File

@@ -0,0 +1,7 @@
1
+---
2
+features:
3
+  - |
4
+    [`bp unified-limits <https://blueprints.launchpad.net/keystone/+spec/unified-limit>`_]
5
+    Support has been added for managing registered limits in keystone via the
6
+    ``registered limit`` command. Registered limits define limits of resources
7
+    for projects to assume by default.

+ 1
- 1
requirements.txt View File

@@ -12,6 +12,6 @@ osc-lib>=1.10.0 # Apache-2.0
12 12
 oslo.i18n>=3.15.3 # Apache-2.0
13 13
 oslo.utils>=3.33.0 # Apache-2.0
14 14
 python-glanceclient>=2.8.0 # Apache-2.0
15
-python-keystoneclient>=3.15.0 # Apache-2.0
15
+python-keystoneclient>=3.17.0 # Apache-2.0
16 16
 python-novaclient>=9.1.0 # Apache-2.0
17 17
 python-cinderclient>=3.3.0 # Apache-2.0

+ 6
- 0
setup.cfg View File

@@ -301,6 +301,12 @@ openstack.identity.v3 =
301 301
     region_set = openstackclient.identity.v3.region:SetRegion
302 302
     region_show = openstackclient.identity.v3.region:ShowRegion
303 303
 
304
+    registered_limit_create = openstackclient.identity.v3.registered_limit:CreateRegisteredLimit
305
+    registered_limit_delete = openstackclient.identity.v3.registered_limit:DeleteRegisteredLimit
306
+    registered_limit_list = openstackclient.identity.v3.registered_limit:ListRegisteredLimit
307
+    registered_limit_set = openstackclient.identity.v3.registered_limit:SetRegisteredLimit
308
+    registered_limit_show = openstackclient.identity.v3.registered_limit:ShowRegisteredLimit
309
+
304 310
     request_token_authorize = openstackclient.identity.v3.token:AuthorizeRequestToken
305 311
     request_token_create = openstackclient.identity.v3.token:CreateRequestToken
306 312
 

Loading…
Cancel
Save