Browse Source

OSC Quota List

Implement Neutron feature of Quota List into
OpenStack Client.

Change-Id: Idf941acf8d00b136776b7381b877c56d82622f57
Partially-Implements: blueprint neutron-client-quota
tags/3.10.0
Sindhu Devale 2 years ago
parent
commit
58591d3c37

+ 23
- 0
doc/source/command-objects/quota.rst View File

@@ -7,6 +7,29 @@ single object with multiple properties.
7 7
 
8 8
 Block Storage v1, v2, Compute v2, Network v2
9 9
 
10
+quota list
11
+----------
12
+
13
+List quotas for all projects with non-default quota values
14
+
15
+.. program:: quota list
16
+.. code:: bash
17
+
18
+    openstack quota list
19
+        --compute | --network | --volume
20
+
21
+.. option:: --network
22
+
23
+    List network quotas
24
+
25
+.. option:: --compute
26
+
27
+    List compute quotas
28
+
29
+.. option:: --volume
30
+
31
+    List volume quotas
32
+
10 33
 quota set
11 34
 ---------
12 35
 

+ 173
- 0
openstackclient/common/quota.py View File

@@ -16,6 +16,7 @@
16 16
 """Quota action implementations"""
17 17
 
18 18
 import itertools
19
+import logging
19 20
 import sys
20 21
 
21 22
 from osc_lib.command import command
@@ -25,6 +26,8 @@ import six
25 26
 from openstackclient.i18n import _
26 27
 
27 28
 
29
+LOG = logging.getLogger(__name__)
30
+
28 31
 # List the quota items, map the internal argument name to the option
29 32
 # name that the user sees.
30 33
 
@@ -78,6 +81,176 @@ NETWORK_QUOTAS = {
78 81
     'l7policy': 'l7policies',
79 82
 }
80 83
 
84
+NETWORK_KEYS = ['floating_ips', 'networks', 'rbac_policies', 'routers',
85
+                'ports', 'security_group_rules', 'security_groups',
86
+                'subnet_pools', 'subnets']
87
+
88
+
89
+def _xform_get_quota(data, value, keys):
90
+    res = []
91
+    res_info = {}
92
+    for key in keys:
93
+        res_info[key] = getattr(data, key, '')
94
+
95
+    res_info['id'] = value
96
+    res.append(res_info)
97
+    return res
98
+
99
+
100
+class ListQuota(command.Lister):
101
+    _description = _("List quotas for all projects "
102
+                     "with non-default quota values")
103
+
104
+    def get_parser(self, prog_name):
105
+        parser = super(ListQuota, self).get_parser(prog_name)
106
+        option = parser.add_mutually_exclusive_group(required=True)
107
+        option.add_argument(
108
+            '--compute',
109
+            action='store_true',
110
+            default=False,
111
+            help=_('List compute quota'),
112
+        )
113
+        option.add_argument(
114
+            '--volume',
115
+            action='store_true',
116
+            default=False,
117
+            help=_('List volume quota'),
118
+        )
119
+        option.add_argument(
120
+            '--network',
121
+            action='store_true',
122
+            default=False,
123
+            help=_('List network quota'),
124
+        )
125
+        return parser
126
+
127
+    def take_action(self, parsed_args):
128
+        projects = self.app.client_manager.identity.projects.list()
129
+        result = []
130
+        project_ids = [getattr(p, 'id', '') for p in projects]
131
+
132
+        if parsed_args.compute:
133
+            compute_client = self.app.client_manager.compute
134
+            for p in project_ids:
135
+                data = compute_client.quotas.get(p)
136
+                result_data = _xform_get_quota(data, p,
137
+                                               COMPUTE_QUOTAS.keys())
138
+                default_data = compute_client.quotas.defaults(p)
139
+                result_default = _xform_get_quota(default_data,
140
+                                                  p,
141
+                                                  COMPUTE_QUOTAS.keys())
142
+                if result_default != result_data:
143
+                    result += result_data
144
+
145
+            columns = (
146
+                'id',
147
+                'cores',
148
+                'fixed_ips',
149
+                'injected_files',
150
+                'injected_file_content_bytes',
151
+                'injected_file_path_bytes',
152
+                'instances',
153
+                'key_pairs',
154
+                'metadata_items',
155
+                'ram',
156
+                'server_groups',
157
+                'server_group_members',
158
+            )
159
+            column_headers = (
160
+                'Project ID',
161
+                'Cores',
162
+                'Fixed IPs',
163
+                'Injected Files',
164
+                'Injected File Content Bytes',
165
+                'Injected File Path Bytes',
166
+                'Instances',
167
+                'Key Pairs',
168
+                'Metadata Items',
169
+                'Ram',
170
+                'Server Groups',
171
+                'Server Group Members',
172
+            )
173
+            return (column_headers,
174
+                    (utils.get_dict_properties(
175
+                        s, columns,
176
+                    ) for s in result))
177
+        if parsed_args.volume:
178
+            volume_client = self.app.client_manager.volume
179
+            for p in project_ids:
180
+                data = volume_client.quotas.get(p)
181
+                result_data = _xform_get_quota(data, p,
182
+                                               VOLUME_QUOTAS.keys())
183
+                default_data = volume_client.quotas.defaults(p)
184
+                result_default = _xform_get_quota(default_data,
185
+                                                  p,
186
+                                                  VOLUME_QUOTAS.keys())
187
+                if result_default != result_data:
188
+                    result += result_data
189
+
190
+            columns = (
191
+                'id',
192
+                'backups',
193
+                'backup_gigabytes',
194
+                'gigabytes',
195
+                'per_volume_gigabytes',
196
+                'snapshots',
197
+                'volumes',
198
+            )
199
+            column_headers = (
200
+                'Project ID',
201
+                'Backups',
202
+                'Backup Gigabytes',
203
+                'Gigabytes',
204
+                'Per Volume Gigabytes',
205
+                'Snapshots',
206
+                'Volumes',
207
+            )
208
+            return (column_headers,
209
+                    (utils.get_dict_properties(
210
+                        s, columns,
211
+                    ) for s in result))
212
+        if parsed_args.network:
213
+            client = self.app.client_manager.network
214
+            for p in project_ids:
215
+                data = client.get_quota(p)
216
+                result_data = _xform_get_quota(data, p, NETWORK_KEYS)
217
+                default_data = client.get_quota_default(p)
218
+                result_default = _xform_get_quota(default_data,
219
+                                                  p, NETWORK_KEYS)
220
+                if result_default != result_data:
221
+                    result += result_data
222
+
223
+            columns = (
224
+                'id',
225
+                'floating_ips',
226
+                'networks',
227
+                'ports',
228
+                'rbac_policies',
229
+                'routers',
230
+                'security_groups',
231
+                'security_group_rules',
232
+                'subnets',
233
+                'subnet_pools',
234
+            )
235
+            column_headers = (
236
+                'Project ID',
237
+                'Floating IPs',
238
+                'Networks',
239
+                'Ports',
240
+                'RBAC Policies',
241
+                'Routers',
242
+                'Security Groups',
243
+                'Security Group Rules',
244
+                'Subnets',
245
+                'Subnet Pools'
246
+            )
247
+            return (column_headers,
248
+                    (utils.get_dict_properties(
249
+                        s, columns,
250
+                    ) for s in result))
251
+
252
+        return ((), ())
253
+
81 254
 
82 255
 class SetQuota(command.Command):
83 256
     _description = _("Set quotas for project or class")

+ 21
- 0
openstackclient/tests/functional/common/test_quota.py View File

@@ -25,6 +25,27 @@ class QuotaTests(base.TestCase):
25 25
         cls.PROJECT_NAME =\
26 26
             cls.get_openstack_configuration_value('auth.project_name')
27 27
 
28
+    def test_quota_list_network_option(self):
29
+        self.openstack('quota set --networks 40 ' +
30
+                       self.PROJECT_NAME)
31
+        raw_output = self.openstack('quota list --network')
32
+        self.assertIsNotNone(raw_output)
33
+        self.assertIn("40", raw_output)
34
+
35
+    def test_quota_list_compute_option(self):
36
+        self.openstack('quota set --instances 40 ' +
37
+                       self.PROJECT_NAME)
38
+        raw_output = self.openstack('quota list --compute')
39
+        self.assertIsNotNone(raw_output)
40
+        self.assertIn("40", raw_output)
41
+
42
+    def test_quota_list_volume_option(self):
43
+        self.openstack('quota set --backups 40 ' +
44
+                       self.PROJECT_NAME)
45
+        raw_output = self.openstack('quota list --volume')
46
+        self.assertIsNotNone(raw_output)
47
+        self.assertIn("40", raw_output)
48
+
28 49
     def test_quota_set(self):
29 50
         self.openstack('quota set --instances 11 --volumes 11 --networks 11 ' +
30 51
                        self.PROJECT_NAME)

+ 159
- 0
openstackclient/tests/unit/common/test_quota.py View File

@@ -19,6 +19,7 @@ from openstackclient.common import quota
19 19
 from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
20 20
 from openstackclient.tests.unit import fakes
21 21
 from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes
22
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3
22 23
 from openstackclient.tests.unit.network.v2 import fakes as network_fakes
23 24
 from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes
24 25
 
@@ -530,3 +531,161 @@ class TestQuotaShow(TestQuota):
530 531
         self.network.get_quota.assert_called_once_with(
531 532
             identity_fakes.project_id)
532 533
         self.assertNotCalled(self.network.get_quota_default)
534
+
535
+
536
+class TestQuotaList(TestQuota):
537
+    """Test cases for quota list command"""
538
+
539
+    project = identity_fakes_v3.FakeProject.create_one_project()
540
+
541
+    quota_list = network_fakes.FakeQuota.create_one_net_quota()
542
+    quota_list1 = compute_fakes.FakeQuota.create_one_comp_quota()
543
+    quota_list2 = volume_fakes.FakeQuota.create_one_vol_quota()
544
+
545
+    default_quota = network_fakes.FakeQuota.create_one_default_net_quota()
546
+    default_quota1 = compute_fakes.FakeQuota.create_one_default_comp_quota()
547
+    default_quota2 = volume_fakes.FakeQuota.create_one_default_vol_quota()
548
+
549
+    reference_data = (project.id,
550
+                      quota_list.floating_ips,
551
+                      quota_list.networks,
552
+                      quota_list.ports,
553
+                      quota_list.rbac_policies,
554
+                      quota_list.routers,
555
+                      quota_list.security_groups,
556
+                      quota_list.security_group_rules,
557
+                      quota_list.subnets,
558
+                      quota_list.subnet_pools)
559
+
560
+    comp_reference_data = (project.id,
561
+                           quota_list1.cores,
562
+                           quota_list1.fixed_ips,
563
+                           quota_list1.injected_files,
564
+                           quota_list1.injected_file_content_bytes,
565
+                           quota_list1.injected_file_path_bytes,
566
+                           quota_list1.instances,
567
+                           quota_list1.key_pairs,
568
+                           quota_list1.metadata_items,
569
+                           quota_list1.ram,
570
+                           quota_list1.server_groups,
571
+                           quota_list1.server_group_members)
572
+
573
+    vol_reference_data = (project.id,
574
+                          quota_list2.backups,
575
+                          quota_list2.backup_gigabytes,
576
+                          quota_list2.gigabytes,
577
+                          quota_list2.per_volume_gigabytes,
578
+                          quota_list2.snapshots,
579
+                          quota_list2.volumes)
580
+
581
+    net_column_header = (
582
+        'Project ID',
583
+        'Floating IPs',
584
+        'Networks',
585
+        'Ports',
586
+        'RBAC Policies',
587
+        'Routers',
588
+        'Security Groups',
589
+        'Security Group Rules',
590
+        'Subnets',
591
+        'Subnet Pools'
592
+    )
593
+
594
+    comp_column_header = (
595
+        'Project ID',
596
+        'Cores',
597
+        'Fixed IPs',
598
+        'Injected Files',
599
+        'Injected File Content Bytes',
600
+        'Injected File Path Bytes',
601
+        'Instances',
602
+        'Key Pairs',
603
+        'Metadata Items',
604
+        'Ram',
605
+        'Server Groups',
606
+        'Server Group Members',
607
+    )
608
+
609
+    vol_column_header = (
610
+        'Project ID',
611
+        'Backups',
612
+        'Backup Gigabytes',
613
+        'Gigabytes',
614
+        'Per Volume Gigabytes',
615
+        'Snapshots',
616
+        'Volumes',
617
+    )
618
+
619
+    def setUp(self):
620
+        super(TestQuotaList, self).setUp()
621
+
622
+        self.projects_mock.get.return_value = fakes.FakeResource(
623
+            None,
624
+            copy.deepcopy(identity_fakes.PROJECT),
625
+            loaded=True,
626
+        )
627
+
628
+        self.identity = self.app.client_manager.identity
629
+        self.identity.tenants.list = mock.Mock(return_value=[self.project])
630
+
631
+        self.network = self.app.client_manager.network
632
+        self.compute = self.app.client_manager.compute
633
+        self.volume = self.app.client_manager.volume
634
+
635
+        self.network.get_quota = mock.Mock(return_value=self.quota_list)
636
+        self.compute.quotas.get = mock.Mock(return_value=self.quota_list1)
637
+        self.volume.quotas.get = mock.Mock(return_value=self.quota_list2)
638
+
639
+        self.network.get_quota_default = mock.Mock(
640
+            return_value=self.default_quota)
641
+        self.compute.quotas.defaults = mock.Mock(
642
+            return_value=self.default_quota1)
643
+        self.volume.quotas.defaults = mock.Mock(
644
+            return_value=self.default_quota2)
645
+
646
+        self.cmd = quota.ListQuota(self.app, None)
647
+
648
+    def test_quota_list_network(self):
649
+        arglist = [
650
+            '--network'
651
+        ]
652
+        verifylist = [
653
+            ('network', True)
654
+        ]
655
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
656
+
657
+        columns, data = self.cmd.take_action(parsed_args)
658
+
659
+        self.assertEqual(self.net_column_header, columns)
660
+
661
+        self.assertEqual(self.reference_data, list(data)[0])
662
+
663
+    def test_quota_list_compute(self):
664
+        arglist = [
665
+            '--compute'
666
+        ]
667
+        verifylist = [
668
+            ('compute', True)
669
+        ]
670
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
671
+
672
+        columns, data = self.cmd.take_action(parsed_args)
673
+
674
+        self.assertEqual(self.comp_column_header, columns)
675
+
676
+        self.assertEqual(self.comp_reference_data, list(data)[0])
677
+
678
+    def test_quota_list_volume(self):
679
+        arglist = [
680
+            '--volume'
681
+        ]
682
+        verifylist = [
683
+            ('volume', True)
684
+        ]
685
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
686
+
687
+        columns, data = self.cmd.take_action(parsed_args)
688
+
689
+        self.assertEqual(self.vol_column_header, columns)
690
+
691
+        self.assertEqual(self.vol_reference_data, list(data)[0])

+ 64
- 0
openstackclient/tests/unit/compute/v2/fakes.py View File

@@ -1313,3 +1313,67 @@ class FakeUsage(object):
1313 1313
             usages.append(FakeUsage.create_one_usage(attrs))
1314 1314
 
1315 1315
         return usages
1316
+
1317
+
1318
+class FakeQuota(object):
1319
+    """Fake quota"""
1320
+
1321
+    @staticmethod
1322
+    def create_one_comp_quota(attrs=None):
1323
+        """Create one quota"""
1324
+
1325
+        attrs = attrs or {}
1326
+
1327
+        quota_attrs = {
1328
+            'id': 'project-id-' + uuid.uuid4().hex,
1329
+            'cores': 20,
1330
+            'fixed_ips': 30,
1331
+            'injected_files': 100,
1332
+            'injected_file_content_bytes': 10240,
1333
+            'injected_file_path_bytes': 255,
1334
+            'instances': 50,
1335
+            'key_pairs': 20,
1336
+            'metadata_items': 10,
1337
+            'ram': 51200,
1338
+            'server_groups': 10,
1339
+            'server_group_members': 10
1340
+        }
1341
+
1342
+        quota_attrs.update(attrs)
1343
+        quota = fakes.FakeResource(
1344
+            info=copy.deepcopy(quota_attrs),
1345
+            loaded=True)
1346
+
1347
+        quota.project_id = quota_attrs['id']
1348
+
1349
+        return quota
1350
+
1351
+    @staticmethod
1352
+    def create_one_default_comp_quota(attrs=None):
1353
+        """Crate one quota"""
1354
+
1355
+        attrs = attrs or {}
1356
+
1357
+        quota_attrs = {
1358
+            'id': 'project-id-' + uuid.uuid4().hex,
1359
+            'cores': 10,
1360
+            'fixed_ips': 10,
1361
+            'injected_files': 100,
1362
+            'injected_file_content_bytes': 10240,
1363
+            'injected_file_path_bytes': 255,
1364
+            'instances': 20,
1365
+            'key_pairs': 20,
1366
+            'metadata_items': 10,
1367
+            'ram': 51200,
1368
+            'server_groups': 10,
1369
+            'server_group_members': 10
1370
+        }
1371
+
1372
+        quota_attrs.update(attrs)
1373
+        quota = fakes.FakeResource(
1374
+            info=copy.deepcopy(quota_attrs),
1375
+            loaded=True)
1376
+
1377
+        quota.project_id = quota_attrs['id']
1378
+
1379
+        return quota

+ 50
- 0
openstackclient/tests/unit/network/v2/fakes.py View File

@@ -1494,3 +1494,53 @@ class FakeNetworkServiceProvider(object):
1494 1494
                                      create_one_network_service_provider(
1495 1495
                                          attrs))
1496 1496
         return service_providers
1497
+
1498
+
1499
+class FakeQuota(object):
1500
+    """Fake quota"""
1501
+
1502
+    @staticmethod
1503
+    def create_one_net_quota(attrs=None):
1504
+        """Create one quota"""
1505
+        attrs = attrs or {}
1506
+
1507
+        quota_attrs = {
1508
+            'floating_ips': 20,
1509
+            'networks': 25,
1510
+            'ports': 11,
1511
+            'rbac_policies': 15,
1512
+            'routers': 40,
1513
+            'security_groups': 10,
1514
+            'security_group_rules': 100,
1515
+            'subnets': 20,
1516
+            'subnet_pools': 30}
1517
+
1518
+        quota_attrs.update(attrs)
1519
+
1520
+        quota = fakes.FakeResource(
1521
+            info=copy.deepcopy(quota_attrs),
1522
+            loaded=True)
1523
+        return quota
1524
+
1525
+    @staticmethod
1526
+    def create_one_default_net_quota(attrs=None):
1527
+        """Create one quota"""
1528
+        attrs = attrs or {}
1529
+
1530
+        quota_attrs = {
1531
+            'floatingip': 30,
1532
+            'network': 20,
1533
+            'port': 10,
1534
+            'rbac_policy': 25,
1535
+            'router': 30,
1536
+            'security_group': 30,
1537
+            'security_group_rule': 200,
1538
+            'subnet': 10,
1539
+            'subnetpool': 20}
1540
+
1541
+        quota_attrs.update(attrs)
1542
+
1543
+        quota = fakes.FakeResource(
1544
+            info=copy.deepcopy(quota_attrs),
1545
+            loaded=True)
1546
+        return quota

+ 50
- 0
openstackclient/tests/unit/volume/v2/fakes.py View File

@@ -954,3 +954,53 @@ class FakeType(object):
954 954
             info=copy.deepcopy(encryption_info),
955 955
             loaded=True)
956 956
         return encryption_type
957
+
958
+
959
+class FakeQuota(object):
960
+    """Fake quota"""
961
+
962
+    @staticmethod
963
+    def create_one_vol_quota(attrs=None):
964
+        """Create one quota"""
965
+        attrs = attrs or {}
966
+
967
+        quota_attrs = {
968
+            'id': 'project-id-' + uuid.uuid4().hex,
969
+            'backups': 100,
970
+            'backup_gigabytes': 100,
971
+            'gigabytes': 10,
972
+            'per_volume_gigabytes': 10,
973
+            'snapshots': 0,
974
+            'volumes': 10}
975
+
976
+        quota_attrs.update(attrs)
977
+
978
+        quota = fakes.FakeResource(
979
+            info=copy.deepcopy(quota_attrs),
980
+            loaded=True)
981
+        quota.project_id = quota_attrs['id']
982
+
983
+        return quota
984
+
985
+    @staticmethod
986
+    def create_one_default_vol_quota(attrs=None):
987
+        """Create one quota"""
988
+        attrs = attrs or {}
989
+
990
+        quota_attrs = {
991
+            'id': 'project-id-' + uuid.uuid4().hex,
992
+            'backups': 100,
993
+            'backup_gigabytes': 100,
994
+            'gigabytes': 100,
995
+            'per_volume_gigabytes': 100,
996
+            'snapshots': 100,
997
+            'volumes': 100}
998
+
999
+        quota_attrs.update(attrs)
1000
+
1001
+        quota = fakes.FakeResource(
1002
+            info=copy.deepcopy(quota_attrs),
1003
+            loaded=True)
1004
+        quota.project_id = quota_attrs['id']
1005
+
1006
+        return quota

+ 6
- 0
releasenotes/notes/add-quota-list-command-0d865fac61db2430.yaml View File

@@ -0,0 +1,6 @@
1
+---
2
+features:
3
+  - |
4
+    Add ``quota list`` command with ``--compute``, ``--volume``
5
+    and ``--network`` options.
6
+    [Blueprint `quota-list <https://blueprints.launchpad.net/python-openstackclient/+spec/neutron-client-quota>`_]

+ 1
- 0
setup.cfg View File

@@ -47,6 +47,7 @@ openstack.common =
47 47
     configuration_show = openstackclient.common.configuration:ShowConfiguration
48 48
     extension_list = openstackclient.common.extension:ListExtension
49 49
     limits_show = openstackclient.common.limits:ShowLimits
50
+    quota_list = openstackclient.common.quota:ListQuota
50 51
     quota_set = openstackclient.common.quota:SetQuota
51 52
     quota_show = openstackclient.common.quota:ShowQuota
52 53
 

Loading…
Cancel
Save