diff --git a/manila_ui/api/manila.py b/manila_ui/api/manila.py index d221d7c4..e2442fea 100644 --- a/manila_ui/api/manila.py +++ b/manila_ui/api/manila.py @@ -306,7 +306,20 @@ def tenant_quota_get(request, tenant_id): return base.QuotaSet(manilaclient(request).quotas.get(tenant_id)) +def _map_quota_names_for_update(data): + mapping = { + 'share_gigabytes': 'gigabytes', + 'share_snapshots': 'snapshots', + 'share_snapshot_gigabytes': 'snapshot_gigabytes', + } + for k, v in mapping.items(): + if k in data: + data[v] = data.pop(k) + return data + + def tenant_quota_update(request, tenant_id, **kwargs): + _map_quota_names_for_update(kwargs) return manilaclient(request).quotas.update(tenant_id, **kwargs) @@ -315,6 +328,7 @@ def default_quota_get(request, tenant_id): def default_quota_update(request, **kwargs): + _map_quota_names_for_update(kwargs) manilaclient(request).quota_classes.update(DEFAULT_QUOTA_NAME, **kwargs) diff --git a/manila_ui/dashboards/project/shares/__init__.py b/manila_ui/dashboards/project/shares/__init__.py index c2749ec4..466ded29 100644 --- a/manila_ui/dashboards/project/shares/__init__.py +++ b/manila_ui/dashboards/project/shares/__init__.py @@ -69,16 +69,20 @@ def wrap(orig_func): MANILA_QUOTA_FIELDS = ( "shares", - "snapshots", - "gigabytes", + "share_gigabytes", + "share_snapshots", + "share_snapshot_gigabytes", "share_networks", ) MANILA_QUOTA_NAMES = { 'shares': _('Shares'), + 'share_gigabytes': _('Share gigabytes'), + 'share_snapshots': _('Share snapshots'), + 'share_snapshot_gigabytes': _('Share snapshot gigabytes'), 'share_networks': _('Shares Networks'), } -quotas.QUOTA_FIELDS = quotas.QUOTA_FIELDS + MANILA_QUOTA_FIELDS +quotas.QUOTA_FIELDS += MANILA_QUOTA_FIELDS def _get_manila_disabled_quotas(request): @@ -96,7 +100,15 @@ def _get_manila_quota_data(request, method_name, disabled_quotas=None, if disabled_quotas is None: disabled_quotas = _get_manila_disabled_quotas(request) if 'shares' not in disabled_quotas: - return getattr(manila, method_name)(request, tenant_id) + manila_quotas = getattr(manila, method_name)(request, tenant_id) + for quota in manila_quotas: + if quota.name == 'gigabytes': + quota.name = 'share_gigabytes' + elif quota.name == 'snapshots': + quota.name = 'share_snapshots' + elif quota.name == 'snapshot_gigabytes': + quota.name = 'share_snapshot_gigabytes' + return manila_quotas else: return None @@ -141,9 +153,10 @@ def tenant_quota_usages(f, request, tenant_id=None): sn_l = manila.share_network_list(request) gig_s = sum([int(v.size) for v in shares]) gig_ss = sum([int(v.size) for v in snapshots]) - usages.tally('gigabytes', gig_s + gig_ss) usages.tally('shares', len(shares)) - usages.tally('snapshots', len(snapshots)) + usages.tally('share_gigabytes', gig_s) + usages.tally('share_snapshots', len(snapshots)) + usages.tally('share_snapshot_gigabytes', gig_ss) usages.tally('share_networks', len(sn_l)) return usages @@ -159,11 +172,14 @@ def tenant_limit_usages(f, request): limits.update(manila.tenant_absolute_limits(request)) shares = manila.share_list(request) snapshots = manila.share_snapshot_list(request) + share_networks = manila.share_network_list(request) total_s_size = sum([getattr(share, 'size', 0) for share in shares]) total_ss_size = sum([getattr(ss, 'size', 0) for ss in snapshots]) - limits['totalGigabytesUsed'] = total_s_size + total_ss_size limits['totalSharesUsed'] = len(shares) + limits['totalShareGigabytesUsed'] = total_s_size limits['totalSnapshotsUsed'] = len(snapshots) + limits['totalSnapshotGigabytesUsed'] = total_ss_size + limits['totalShareNetworksUsed'] = len(share_networks) except Exception: msg = _("Unable to retrieve share limit information.") horizon.exceptions.handle(request, msg) @@ -187,11 +203,17 @@ def get_quota_name(f, quota): class ManilaUpdateDefaultQuotaAction( default_workflows.UpdateDefaultQuotasAction): shares = horizon.forms.IntegerField(min_value=-1, label=_("Shares")) - share_networks = horizon.forms.IntegerField(min_value=-1, - label=_("Share Networks")) + share_gigabytes = horizon.forms.IntegerField( + min_value=-1, label=_("Share gigabytes")) + share_snapshots = horizon.forms.IntegerField( + min_value=-1, label=_("Share snapshots")) + share_snapshot_gigabytes = horizon.forms.IntegerField( + min_value=-1, label=_("Share snapshot gigabytes")) + share_networks = horizon.forms.IntegerField( + min_value=-1, label=_("Share Networks")) class Meta(object): - name = _("Default Quota") + name = _("Default Quotas") slug = 'update_default_quotas' help_text = _("From here you can update the default quotas " "(max limits).") @@ -221,8 +243,8 @@ class ManilaUpdateDefaultQuotas(default_workflows.UpdateDefaultQuotas): return True -default_views.UpdateDefaultQuotasView.workflow_class = \ - ManilaUpdateDefaultQuotas +default_views.UpdateDefaultQuotasView.workflow_class = ( + ManilaUpdateDefaultQuotas) # # Add manila fields to Identity/Projects/Modify Quotas @@ -232,16 +254,22 @@ default_views.UpdateDefaultQuotasView.workflow_class = \ class ManilaUpdateProjectQuotaAction( project_workflows.UpdateProjectQuotaAction): shares = horizon.forms.IntegerField(min_value=-1, label=_("Shares")) - share_networks = horizon.forms.IntegerField(min_value=-1, - label=_("Share Networks")) + share_gigabytes = horizon.forms.IntegerField( + min_value=-1, label=_("Share gigabytes")) + share_snapshots = horizon.forms.IntegerField( + min_value=-1, label=_("Share snapshots")) + share_snapshot_gigabytes = horizon.forms.IntegerField( + min_value=-1, label=_("Share snapshot gigabytes")) + share_networks = horizon.forms.IntegerField( + min_value=-1, label=_("Share Networks")) class Meta(object): name = _("Quota") slug = 'update_quotas' help_text = _("Set maximum quotas for the project.") -project_workflows.UpdateProjectQuota.action_class = \ - ManilaUpdateProjectQuotaAction +project_workflows.UpdateProjectQuota.action_class = ( + ManilaUpdateProjectQuotaAction) project_workflows.UpdateProjectQuota.contributes = quotas.QUOTA_FIELDS @@ -274,16 +302,23 @@ project_views.UpdateProjectView.workflow_class = ManilaUpdateProject class ManilaCreateProjectQuotaAction( project_workflows.CreateProjectQuotaAction): shares = horizon.forms.IntegerField(min_value=-1, label=_("Shares")) - share_networks = horizon.forms.IntegerField(min_value=-1, - label=_("Share Networks")) + share_gigabytes = horizon.forms.IntegerField( + min_value=-1, label=_("Share gigabytes")) + share_snapshots = horizon.forms.IntegerField( + min_value=-1, label=_("Share snapshots")) + share_snapshot_gigabytes = horizon.forms.IntegerField( + min_value=-1, label=_("Share snapshot gigabytes")) + share_networks = horizon.forms.IntegerField( + min_value=-1, label=_("Share Networks")) class Meta(object): name = _("Quota") slug = 'create_quotas' help_text = _("Set maximum quotas for the project.") -project_workflows.CreateProjectQuota.action_class = \ - ManilaCreateProjectQuotaAction + +project_workflows.CreateProjectQuota.action_class = ( + ManilaCreateProjectQuotaAction) project_workflows.CreateProjectQuota.contributes = quotas.QUOTA_FIELDS diff --git a/manila_ui/tests/api/test_manila.py b/manila_ui/tests/api/test_manila.py index 8219d502..02e7ab5d 100644 --- a/manila_ui/tests/api/test_manila.py +++ b/manila_ui/tests/api/test_manila.py @@ -249,3 +249,68 @@ class ManilaApiTests(base.APITestCase): (self.manilaclient.shares.migration_get_progress. assert_called_once_with('fake_share')) + + @ddt.data( + ({'share_gigabytes': 333}, {'gigabytes': 333}), + ({'share_snapshot_gigabytes': 444}, {'snapshot_gigabytes': 444}), + ({'share_snapshots': 14}, {'snapshots': 14}), + ({'snapshots': 14}, {'snapshots': 14}), + ({'gigabytes': 14}, {'gigabytes': 14}), + ({'snapshot_gigabytes': 314}, {'snapshot_gigabytes': 314}), + ({'shares': 24}, {'shares': 24}), + ({'share_networks': 14}, {'share_networks': 14}), + ) + @ddt.unpack + def test_tenant_quota_update(self, provided_kwargs, expected_kwargs): + tenant_id = 'fake_tenant_id' + + api.tenant_quota_update(self.request, tenant_id, **provided_kwargs) + + self.manilaclient.quotas.update.assert_called_once_with( + tenant_id, **expected_kwargs) + self.manilaclient.quota_classes.update.assert_not_called() + + @ddt.data( + ({'share_gigabytes': 333}, {'gigabytes': 333}), + ({'share_snapshot_gigabytes': 444}, {'snapshot_gigabytes': 444}), + ({'share_snapshots': 14}, {'snapshots': 14}), + ({'snapshots': 14}, {'snapshots': 14}), + ({'gigabytes': 14}, {'gigabytes': 14}), + ({'snapshot_gigabytes': 314}, {'snapshot_gigabytes': 314}), + ({'shares': 24}, {'shares': 24}), + ({'share_networks': 14}, {'share_networks': 14}), + ) + @ddt.unpack + def test_default_quota_update(self, provided_kwargs, expected_kwargs): + api.default_quota_update(self.request, **provided_kwargs) + + self.manilaclient.quota_classes.update.assert_called_once_with( + api.DEFAULT_QUOTA_NAME, **expected_kwargs) + + @ddt.data( + {}, + {"name": "foo_name"}, + {"description": "foo_desc"}, + {"neutron_net_id": "foo_neutron_net_id"}, + {"neutron_subnet_id": "foo_neutron_subnet_id"}, + {"nova_net_id": "foo_nova_net_id"}, + {"name": "foo_name", "description": "foo_desc", + "neutron_net_id": "foo_neutron_net_id", + "neutron_subnet_id": "foo_neutron_subnet_id", + "nova_net_id": "foo_nova_net_id"}, + ) + @ddt.unpack + def test_share_network_create(self, **kwargs): + expected_kwargs = { + "name": None, + "description": None, + "neutron_net_id": None, + "neutron_subnet_id": None, + "nova_net_id": None, + } + expected_kwargs.update(kwargs) + + api.share_network_create(self.request, **kwargs) + + mock_sn_create = self.manilaclient.share_networks.create + mock_sn_create.assert_called_once_with(**expected_kwargs) diff --git a/manila_ui/tests/dashboards/project/shares/tests.py b/manila_ui/tests/dashboards/project/shares/tests.py index 399aee05..9f435935 100644 --- a/manila_ui/tests/dashboards/project/shares/tests.py +++ b/manila_ui/tests/dashboards/project/shares/tests.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import ddt from django.core.urlresolvers import reverse from django.utils import translation import mock @@ -133,3 +134,71 @@ class PieChartsTests(test.TestCase): "text": chart["text"]}, expected_charts.pop(name, "NotFound") ) + + +@ddt.ddt +class QuotaTests(test.TestCase): + + @ddt.data( + shares.ManilaUpdateDefaultQuotaAction, + shares.ManilaUpdateProjectQuotaAction, + shares.ManilaCreateProjectQuotaAction, + ) + def test_manila_quota_action(self, class_ref): + self.mock_object( + quotas, 'get_disabled_quotas', mock.Mock(return_value=[])) + class_instance = class_ref(self.request, 'foo') + expected_fields = set([ + 'shares', 'share_gigabytes', 'share_snapshots', + 'share_snapshot_gigabytes', 'share_networks', + ]) + # NOTE(vponomaryov): iterate over reversed list of visible fields + # because manila's fields are at the end always. + for vf in reversed(class_instance.visible_fields()): + if expected_fields and vf.name in expected_fields: + self.assertEqual(-1, vf.field.min_value) + self.assertIsInstance( + vf.field, shares.horizon.forms.IntegerField) + expected_fields.remove(vf.name) + self.assertSetEqual(set([]), expected_fields) + self.assertTrue(quotas.get_disabled_quotas.called) + + @ddt.data('default_quota_get', 'tenant_quota_get') + def test__get_manila_quota_data(self, method_name): + fake_quotas = [ + type('Fake', (object, ), {'name': name}) + for name in ('gigabytes', 'snapshots', 'snapshot_gigabytes') + ] + self.mock_object( + api_manila, method_name, mock.Mock(return_value=fake_quotas)) + self.mock_object( + shares, '_get_manila_disabled_quotas', + mock.Mock(return_value=[])) + + result = shares._get_manila_quota_data( + self.request, method_name) + + expected = [ + 'share_gigabytes', + 'share_snapshot_gigabytes', + 'share_snapshots', + ] + self.assertEqual(3, len(result)) + self.assertEqual( + expected, + sorted([element.name for element in result])) + getattr(api_manila, method_name).assert_called_once_with( + self.request, self.request.user.tenant_id) + shares._get_manila_disabled_quotas.asssert_called_once_with( + self.request) + + def test_manila_quota_fields(self): + expected_fields = ( + "shares", + "share_gigabytes", + "share_snapshots", + "share_snapshot_gigabytes", + "share_networks", + ) + for ef in expected_fields: + self.assertIn(ef, shares.quotas.QUOTA_FIELDS) diff --git a/releasenotes/notes/bug-1679960-fix-quota-names-overlapping-4277d45cd0110489.yaml b/releasenotes/notes/bug-1679960-fix-quota-names-overlapping-4277d45cd0110489.yaml new file mode 100644 index 00000000..247879d0 --- /dev/null +++ b/releasenotes/notes/bug-1679960-fix-quota-names-overlapping-4277d45cd0110489.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - Fixed quota names overlapping with Cinder's quotas. Now it is possible + to read/write any manila quotas not messing up Cinder's quotas.