From 5f8e370f2dca2d7d12e51f1b44b63e22de05eaa9 Mon Sep 17 00:00:00 2001 From: liyingjun Date: Fri, 24 May 2013 00:01:30 +0800 Subject: [PATCH] Allow admins to update default quotas Add update default quotas form to edit the default quotas. Move the editable default quotas tab to a new panel called "Defaults". Implements blueprint edit-default-quota Change-Id: I6e3806226cd2f699f16b93c25e294bc67883738f --- openstack_dashboard/api/cinder.py | 5 + openstack_dashboard/api/nova.py | 5 + .../dashboards/admin/dashboard.py | 2 +- .../dashboards/admin/defaults/__init__.py | 0 .../dashboards/admin/defaults/panel.py | 29 ++++ .../dashboards/admin/defaults/tables.py | 62 ++++++++ .../dashboards/admin/defaults/tabs.py | 54 +++++++ .../defaults/templates/defaults/index.html | 15 ++ .../dashboards/admin/defaults/tests.py | 140 ++++++++++++++++++ .../dashboards/admin/defaults/urls.py | 26 ++++ .../dashboards/admin/defaults/views.py | 54 +++++++ .../dashboards/admin/defaults/workflows.py | 101 +++++++++++++ .../dashboards/admin/info/panel.py | 4 +- .../dashboards/admin/info/tables.py | 34 ----- .../dashboards/admin/info/tabs.py | 28 +--- .../dashboards/admin/info/tests.py | 95 +----------- openstack_dashboard/usage/quotas.py | 3 + requirements.txt | 2 +- 18 files changed, 501 insertions(+), 158 deletions(-) create mode 100644 openstack_dashboard/dashboards/admin/defaults/__init__.py create mode 100644 openstack_dashboard/dashboards/admin/defaults/panel.py create mode 100644 openstack_dashboard/dashboards/admin/defaults/tables.py create mode 100644 openstack_dashboard/dashboards/admin/defaults/tabs.py create mode 100644 openstack_dashboard/dashboards/admin/defaults/templates/defaults/index.html create mode 100644 openstack_dashboard/dashboards/admin/defaults/tests.py create mode 100644 openstack_dashboard/dashboards/admin/defaults/urls.py create mode 100644 openstack_dashboard/dashboards/admin/defaults/views.py create mode 100644 openstack_dashboard/dashboards/admin/defaults/workflows.py diff --git a/openstack_dashboard/api/cinder.py b/openstack_dashboard/api/cinder.py index b29313f4b4..c8b32bd96d 100644 --- a/openstack_dashboard/api/cinder.py +++ b/openstack_dashboard/api/cinder.py @@ -39,6 +39,7 @@ LOG = logging.getLogger(__name__) # API static values VOLUME_STATE_AVAILABLE = "available" +DEFAULT_QUOTA_NAME = 'default' def cinderclient(request): @@ -134,6 +135,10 @@ def default_quota_get(request, tenant_id): return base.QuotaSet(cinderclient(request).quotas.defaults(tenant_id)) +def default_quota_update(request, **kwargs): + cinderclient(request).quota_classes.update(DEFAULT_QUOTA_NAME, **kwargs) + + def volume_type_list(request): return cinderclient(request).volume_types.list() diff --git a/openstack_dashboard/api/nova.py b/openstack_dashboard/api/nova.py index 641c900a25..164bb2907d 100644 --- a/openstack_dashboard/api/nova.py +++ b/openstack_dashboard/api/nova.py @@ -47,6 +47,7 @@ LOG = logging.getLogger(__name__) # API static values INSTANCE_ACTIVE_STATE = 'ACTIVE' VOLUME_STATE_AVAILABLE = "available" +DEFAULT_QUOTA_NAME = 'default' class VNCConsole(base.APIDictWrapper): @@ -557,6 +558,10 @@ def default_quota_get(request, tenant_id): return base.QuotaSet(novaclient(request).quotas.defaults(tenant_id)) +def default_quota_update(request, **kwargs): + novaclient(request).quota_classes.update(DEFAULT_QUOTA_NAME, **kwargs) + + def usage_get(request, tenant_id, start, end): return NovaUsage(novaclient(request).usage.get(tenant_id, start, end)) diff --git a/openstack_dashboard/dashboards/admin/dashboard.py b/openstack_dashboard/dashboards/admin/dashboard.py index a77807cb37..cf01ca1bdc 100644 --- a/openstack_dashboard/dashboards/admin/dashboard.py +++ b/openstack_dashboard/dashboards/admin/dashboard.py @@ -23,7 +23,7 @@ class SystemPanels(horizon.PanelGroup): slug = "admin" name = _("System Panel") panels = ('overview', 'hypervisors', 'instances', 'volumes', - 'flavors', 'images', 'networks', 'routers', 'info') + 'flavors', 'images', 'networks', 'routers', 'defaults', 'info') class IdentityPanels(horizon.PanelGroup): diff --git a/openstack_dashboard/dashboards/admin/defaults/__init__.py b/openstack_dashboard/dashboards/admin/defaults/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/dashboards/admin/defaults/panel.py b/openstack_dashboard/dashboards/admin/defaults/panel.py new file mode 100644 index 0000000000..4b9130d32c --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/panel.py @@ -0,0 +1,29 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Kylin, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.utils.translation import ugettext_lazy as _ # noqa + +import horizon + +from openstack_dashboard.dashboards.admin import dashboard + + +class Defaults(horizon.Panel): + name = _("Defaults") + slug = 'defaults' + + +dashboard.Admin.register(Defaults) diff --git a/openstack_dashboard/dashboards/admin/defaults/tables.py b/openstack_dashboard/dashboards/admin/defaults/tables.py new file mode 100644 index 0000000000..5a91a2f82b --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/tables.py @@ -0,0 +1,62 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Kylin, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import tables + + +LOG = logging.getLogger(__name__) + + +class QuotaFilterAction(tables.FilterAction): + def filter(self, table, tenants, filter_string): + q = filter_string.lower() + + def comp(tenant): + if q in tenant.name.lower(): + return True + return False + + return filter(comp, tenants) + + +class UpdateDefaultQuotas(tables.LinkAction): + name = "update_defaults" + verbose_name = _("Update Defaults") + url = "horizon:admin:defaults:update_defaults" + classes = ("ajax-modal", "btn-edit") + + +def get_quota_name(quota): + QUOTA_NAMES = {'cores': _('VCPUs'), 'floating_ips': _('Floating IPs')} + return QUOTA_NAMES.get(quota.name, quota.name.replace("_", " ").title()) + + +class QuotasTable(tables.DataTable): + name = tables.Column(get_quota_name, verbose_name=_('Quota Name')) + limit = tables.Column("limit", verbose_name=_('Limit')) + + def get_object_id(self, obj): + return obj.name + + class Meta: + name = "quotas" + verbose_name = _("Quotas") + table_actions = (QuotaFilterAction, UpdateDefaultQuotas) + multi_select = False diff --git a/openstack_dashboard/dashboards/admin/defaults/tabs.py b/openstack_dashboard/dashboards/admin/defaults/tabs.py new file mode 100644 index 0000000000..081f3658b3 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/tabs.py @@ -0,0 +1,54 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Kylin, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import exceptions +from horizon import tabs + +from openstack_dashboard.api import base +from openstack_dashboard.usage import quotas + +from openstack_dashboard.dashboards.admin.defaults import tables + + +class DefaultQuotasTab(tabs.TableTab): + table_classes = (tables.QuotasTable,) + name = _("Default Quotas") + slug = "quotas" + template_name = ("horizon/common/_detail_table.html") + + def get_quotas_data(self): + request = self.tab_group.request + try: + quota_set = quotas.get_default_quota_data(request) + data = quota_set.items + # There is no API to get the default system quotas in + # Neutron (cf. LP#1204956). Remove the network-related + # quotas from the list for now to avoid confusion + if base.is_service_enabled(self.request, 'network'): + data = [quota for quota in data + if quota.name not in ['floating_ips', 'fixed_ips']] + except Exception: + data = [] + exceptions.handle(self.request, _('Unable to get quota info.')) + return data + + +class DefaultsTabs(tabs.TabGroup): + slug = "defaults" + tabs = (DefaultQuotasTab,) + sticky = True diff --git a/openstack_dashboard/dashboards/admin/defaults/templates/defaults/index.html b/openstack_dashboard/dashboards/admin/defaults/templates/defaults/index.html new file mode 100644 index 0000000000..aee673ae28 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/templates/defaults/index.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Defaults" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Defaults")%} +{% endblock page_header %} + +{% block main %} +
+
+ {{ tab_group.render }} +
+
+{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/defaults/tests.py b/openstack_dashboard/dashboards/admin/defaults/tests.py new file mode 100644 index 0000000000..0396defbb9 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/tests.py @@ -0,0 +1,140 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Kylin, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.core.urlresolvers import reverse # noqa +from django import http +from mox import IsA # noqa + +from openstack_dashboard import api +from openstack_dashboard.test import helpers as test +from openstack_dashboard.usage import quotas + +INDEX_URL = reverse('horizon:admin:defaults:index') + + +class ServicesViewTests(test.BaseAdminViewTests): + def test_index(self): + self.mox.StubOutWithMock(api.nova, 'default_quota_get') + self.mox.StubOutWithMock(api.cinder, 'default_quota_get') + api.nova.default_quota_get(IsA(http.HttpRequest), + self.tenant.id).AndReturn(self.quotas.nova) + api.cinder.default_quota_get(IsA(http.HttpRequest), self.tenant.id) \ + .AndReturn(self.cinder_quotas.first()) + + self.mox.ReplayAll() + + res = self.client.get(INDEX_URL) + + self.assertTemplateUsed(res, 'admin/defaults/index.html') + + quotas_tab = res.context['tab_group'].get_tab('quotas') + self.assertQuerysetEqual(quotas_tab._tables['quotas'].data, + ['', + '', + '', + '', + '', + '', + '', + '', + '', + '', + ''], + ordered=False) + + @test.create_stubs({api.base: ('is_service_enabled',), + api.nova: ('default_quota_get', 'service_list',), + api.cinder: ('default_quota_get',)}) + def test_index_with_neutron_disabled(self): + # Neutron does not have an API for getting default system + # quotas. When not using Neutron, the floating ips quotas + # should be in the list. + api.base.is_service_enabled(IsA(http.HttpRequest), 'volume') \ + .AndReturn(True) + api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \ + .MultipleTimes().AndReturn(False) + + api.nova.default_quota_get(IsA(http.HttpRequest), + self.tenant.id).AndReturn(self.quotas.nova) + api.cinder.default_quota_get(IsA(http.HttpRequest), self.tenant.id) \ + .AndReturn(self.cinder_quotas.first()) + + self.mox.ReplayAll() + + res = self.client.get(INDEX_URL) + + self.assertTemplateUsed(res, 'admin/defaults/index.html') + + quotas_tab = res.context['tab_group'].get_tab('quotas') + self.assertQuerysetEqual(quotas_tab._tables['quotas'].data, + ['', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + ''], + ordered=False) + + +class UpdateDefaultQuotasTests(test.BaseAdminViewTests): + def _get_quota_info(self, quota): + quota_data = {} + for field in (quotas.QUOTA_FIELDS + quotas.MISSING_QUOTA_FIELDS): + if field != 'fixed_ips': + limit = quota.get(field).limit or 10 + quota_data[field] = int(limit) + return quota_data + + @test.create_stubs({api.nova: ('default_quota_update', ), + api.cinder: ('default_quota_update', ), + quotas: ('get_default_quota_data', )}) + def test_update_default_quotas(self): + quota = self.quotas.first() + + # init + quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota) + + # update some fields + quota[0].limit = 123 + quota[1].limit = -1 + updated_quota = self._get_quota_info(quota) + + # handle + nova_fields = quotas.NOVA_QUOTA_FIELDS + quotas.MISSING_QUOTA_FIELDS + nova_updated_quota = dict([(key, updated_quota[key]) for key in + nova_fields if key != 'fixed_ips']) + api.nova.default_quota_update(IsA(http.HttpRequest), + **nova_updated_quota) + + cinder_updated_quota = dict([(key, updated_quota[key]) for key in + quotas.CINDER_QUOTA_FIELDS]) + api.cinder.default_quota_update(IsA(http.HttpRequest), + **cinder_updated_quota) + + self.mox.ReplayAll() + + url = reverse('horizon:admin:defaults:update_defaults') + res = self.client.post(url, updated_quota) + + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/openstack_dashboard/dashboards/admin/defaults/urls.py b/openstack_dashboard/dashboards/admin/defaults/urls.py new file mode 100644 index 0000000000..fa551a5641 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/urls.py @@ -0,0 +1,26 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Kylin, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.conf.urls.defaults import patterns # noqa +from django.conf.urls.defaults import url # noqa + +from openstack_dashboard.dashboards.admin.defaults import views + + +urlpatterns = patterns('openstack_dashboard.dashboards.admin.defaults.views', + url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^update_defaults$', + views.UpdateDefaultQuotasView.as_view(), name='update_defaults')) diff --git a/openstack_dashboard/dashboards/admin/defaults/views.py b/openstack_dashboard/dashboards/admin/defaults/views.py new file mode 100644 index 0000000000..99455812f6 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/views.py @@ -0,0 +1,54 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Kylin, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import tabs +from horizon import workflows + +from openstack_dashboard.dashboards.admin.defaults import tabs as project_tabs +from openstack_dashboard.dashboards.admin.defaults import workflows as \ + project_workflows +from openstack_dashboard.usage import quotas + + +LOG = logging.getLogger(__name__) + + +class IndexView(tabs.TabbedTableView): + tab_group_class = project_tabs.DefaultsTabs + template_name = 'admin/defaults/index.html' + + +class UpdateDefaultQuotasView(workflows.WorkflowView): + workflow_class = project_workflows.UpdateDefaultQuotas + + def get_initial(self): + initial = super(UpdateDefaultQuotasView, self).get_initial() + + # get initial quota defaults + try: + quota_defaults = quotas.get_default_quota_data(self.request) + for field in (quotas.QUOTA_FIELDS + quotas.MISSING_QUOTA_FIELDS): + initial[field] = quota_defaults.get(field).limit + + except Exception: + error_msg = _('Unable to retrieve default quota values.') + self.add_error_to_step(error_msg, 'update_default_quotas') + + return initial diff --git a/openstack_dashboard/dashboards/admin/defaults/workflows.py b/openstack_dashboard/dashboards/admin/defaults/workflows.py new file mode 100644 index 0000000000..7840c6a681 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/workflows.py @@ -0,0 +1,101 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Kylin, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import exceptions +from horizon import forms +from horizon import workflows + +from openstack_dashboard.api import base +from openstack_dashboard.api import cinder +from openstack_dashboard.api import nova +from openstack_dashboard.usage import quotas + +ALL_NOVA_QUOTA_FIELDS = quotas.NOVA_QUOTA_FIELDS + quotas.MISSING_QUOTA_FIELDS + + +class UpdateDefaultQuotasAction(workflows.Action): + ifcb_label = _("Injected File Content Bytes") + ifpb_label = _("Injected File Path Bytes") + metadata_items = forms.IntegerField(min_value=-1, + label=_("Metadata Items")) + cores = forms.IntegerField(min_value=-1, label=_("VCPUs")) + instances = forms.IntegerField(min_value=-1, label=_("Instances")) + injected_files = forms.IntegerField(min_value=-1, + label=_("Injected Files")) + injected_file_content_bytes = forms.IntegerField(min_value=-1, + label=ifcb_label) + injected_file_path_bytes = forms.IntegerField(min_value=-1, + label=ifpb_label) + volumes = forms.IntegerField(min_value=-1, label=_("Volumes")) + snapshots = forms.IntegerField(min_value=-1, label=_("Snapshots")) + gigabytes = forms.IntegerField(min_value=-1, label=_("Gigabytes")) + ram = forms.IntegerField(min_value=-1, label=_("RAM (MB)")) + floating_ips = forms.IntegerField(min_value=-1, label=_("Floating IPs")) + security_groups = forms.IntegerField(min_value=-1, + label=_("Security Groups")) + security_group_rules = forms.IntegerField(min_value=-1, + label=_("Security Group Rules")) + key_pairs = forms.IntegerField(min_value=-1, label=_("Key Pairs")) + + def __init__(self, request, *args, **kwargs): + super(UpdateDefaultQuotasAction, self).__init__(request, + *args, + **kwargs) + disabled_quotas = quotas.get_disabled_quotas(request) + for field in disabled_quotas: + if field in self.fields: + self.fields[field].required = False + self.fields[field].widget = forms.HiddenInput() + + class Meta: + name = _("Default Quotas") + slug = 'update_default_quotas' + help_text = _("From here you can update the default quotas " + "(max limits).") + + +class UpdateDefaultQuotas(workflows.Step): + action_class = UpdateDefaultQuotasAction + contributes = (quotas.QUOTA_FIELDS + quotas.MISSING_QUOTA_FIELDS) + + +class UpdateDefaultQuotas(workflows.Workflow): + slug = "update_default_quotas" + name = _("Update Default Quotas") + finalize_button_name = _("Update Defaults") + success_message = _('Default quotas updated "%s".') + failure_message = _('Unable to update default quotas "%s".') + success_url = "horizon:admin:defaults:index" + default_steps = (UpdateDefaultQuotas,) + + def handle(self, request, data): + # Update the default quotas. + # `fixed_ips` update for quota class is not supported by novaclient + nova_data = dict([(key, data[key]) for key in ALL_NOVA_QUOTA_FIELDS + if key != 'fixed_ips']) + try: + nova.default_quota_update(request, **nova_data) + + if base.is_service_enabled(request, 'volume'): + cinder_data = dict([(key, data[key]) for key in + quotas.CINDER_QUOTA_FIELDS]) + cinder.default_quota_update(request, **cinder_data) + except Exception: + exceptions.handle(request, _('Unable to update default quotas.')) + return True diff --git a/openstack_dashboard/dashboards/admin/info/panel.py b/openstack_dashboard/dashboards/admin/info/panel.py index 413903143c..6506578601 100644 --- a/openstack_dashboard/dashboards/admin/info/panel.py +++ b/openstack_dashboard/dashboards/admin/info/panel.py @@ -25,9 +25,9 @@ import horizon from openstack_dashboard.dashboards.admin import dashboard -class Quotas(horizon.Panel): +class Info(horizon.Panel): name = _("System Info") slug = 'info' -dashboard.Admin.register(Quotas) +dashboard.Admin.register(Info) diff --git a/openstack_dashboard/dashboards/admin/info/tables.py b/openstack_dashboard/dashboards/admin/info/tables.py index 0d64ea281f..5c4dfe3732 100644 --- a/openstack_dashboard/dashboards/admin/info/tables.py +++ b/openstack_dashboard/dashboards/admin/info/tables.py @@ -11,40 +11,6 @@ from horizon.utils.filters import parse_isotime # noqa LOG = logging.getLogger(__name__) -class QuotaFilterAction(tables.FilterAction): - def filter(self, table, tenants, filter_string): - q = filter_string.lower() - - def comp(tenant): - if q in tenant.name.lower(): - return True - return False - - return filter(comp, tenants) - - -def get_quota_name(quota): - if quota.name == "cores": - return _('VCPUs') - if quota.name == "floating_ips": - return _('Floating IPs') - return quota.name.replace("_", " ").title() - - -class QuotasTable(tables.DataTable): - name = tables.Column(get_quota_name, verbose_name=_('Quota Name')) - limit = tables.Column("limit", verbose_name=_('Limit')) - - def get_object_id(self, obj): - return obj.name - - class Meta: - name = "quotas" - verbose_name = _("Quotas") - table_actions = (QuotaFilterAction,) - multi_select = False - - class ServiceFilterAction(tables.FilterAction): def filter(self, table, services, filter_string): q = filter_string.lower() diff --git a/openstack_dashboard/dashboards/admin/info/tabs.py b/openstack_dashboard/dashboards/admin/info/tabs.py index fa3c0484a7..37a4afce77 100644 --- a/openstack_dashboard/dashboards/admin/info/tabs.py +++ b/openstack_dashboard/dashboards/admin/info/tabs.py @@ -19,37 +19,12 @@ from django.utils.translation import ugettext_lazy as _ # noqa from horizon import exceptions from horizon import tabs -from openstack_dashboard.api import base from openstack_dashboard.api import keystone from openstack_dashboard.api import nova -from openstack_dashboard.usage import quotas from openstack_dashboard.dashboards.admin.info import tables -class DefaultQuotasTab(tabs.TableTab): - table_classes = (tables.QuotasTable,) - name = _("Default Quotas") - slug = "quotas" - template_name = ("horizon/common/_detail_table.html") - - def get_quotas_data(self): - request = self.tab_group.request - try: - quota_set = quotas.get_default_quota_data(request) - data = quota_set.items - # There is no API to get the default system quotas in - # Neutron (cf. LP#1204956). Remove the network-related - # quotas from the list for now to avoid confusion - if base.is_service_enabled(self.request, 'network'): - data = [quota for quota in data - if quota.name not in ['floating_ips', 'fixed_ips']] - except Exception: - data = [] - exceptions.handle(self.request, _('Unable to get quota info.')) - return data - - class ServicesTab(tabs.TableTab): table_classes = (tables.ServicesTable,) name = _("Services") @@ -119,6 +94,5 @@ class NovaServicesTab(tabs.TableTab): class SystemInfoTabs(tabs.TabGroup): slug = "system_info" - tabs = (ServicesTab, NovaServicesTab, ZonesTab, HostAggregatesTab, - DefaultQuotasTab) + tabs = (ServicesTab, NovaServicesTab, ZonesTab, HostAggregatesTab) sticky = True diff --git a/openstack_dashboard/dashboards/admin/info/tests.py b/openstack_dashboard/dashboards/admin/info/tests.py index a316a54922..b0eeb8a0f5 100644 --- a/openstack_dashboard/dashboards/admin/info/tests.py +++ b/openstack_dashboard/dashboards/admin/info/tests.py @@ -26,16 +26,10 @@ INDEX_URL = reverse('horizon:admin:info:index') class SystemInfoViewTests(test.BaseAdminViewTests): - @test.create_stubs({api.nova: ('default_quota_get', - 'service_list', + @test.create_stubs({api.nova: ('service_list', 'availability_zone_list', - 'aggregate_list'), - api.cinder: ('default_quota_get',)}) + 'aggregate_list')}) def test_index(self): - api.nova.default_quota_get(IsA(http.HttpRequest), - self.tenant.id).AndReturn(self.quotas.nova) - api.cinder.default_quota_get(IsA(http.HttpRequest), self.tenant.id) \ - .AndReturn(self.cinder_quotas.first()) services = self.services.list() api.nova.service_list(IsA(http.HttpRequest)).AndReturn(services) api.nova.availability_zone_list(IsA(http.HttpRequest), detailed=True) \ @@ -61,91 +55,6 @@ class SystemInfoViewTests(test.BaseAdminViewTests): '', '']) - quotas_tab = res.context['tab_group'].get_tab('quotas') - self.assertQuerysetEqual(quotas_tab._tables['quotas'].data, - ['', - '', - '', - '', - '', - '', - '', - '', - '', - '', - ''], - ordered=False) - - zones_tab = res.context['tab_group'].get_tab('zones') - self.assertQuerysetEqual(zones_tab._tables['zones'].data, - ['']) - - aggregates_tab = res.context['tab_group'].get_tab('aggregates') - self.assertQuerysetEqual(aggregates_tab._tables['aggregates'].data, - ['', '']) - - @test.create_stubs({api.base: ('is_service_enabled',), - api.nova: ('default_quota_get', - 'service_list', - 'availability_zone_list', - 'aggregate_list'), - api.cinder: ('default_quota_get',)}) - def test_index_with_neutron_disabled(self): - # Neutron does not have an API for getting default system - # quotas. When not using Neutron, the floating ips quotas - # should be in the list. - api.base.is_service_enabled(IsA(http.HttpRequest), 'volume') \ - .AndReturn(True) - api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \ - .MultipleTimes().AndReturn(False) - - api.nova.default_quota_get(IsA(http.HttpRequest), - self.tenant.id).AndReturn(self.quotas.nova) - - api.cinder.default_quota_get(IsA(http.HttpRequest), self.tenant.id) \ - .AndReturn(self.cinder_quotas.first()) - services = self.services.list() - api.nova.service_list(IsA(http.HttpRequest)).AndReturn(services) - api.nova.availability_zone_list(IsA(http.HttpRequest), detailed=True) \ - .AndReturn(self.availability_zones.list()) - api.nova.aggregate_list(IsA(http.HttpRequest)) \ - .AndReturn(self.aggregates.list()) - - self.mox.ReplayAll() - - res = self.client.get(INDEX_URL) - - self.assertTemplateUsed(res, 'admin/info/index.html') - - services_tab = res.context['tab_group'].get_tab('services') - self.assertQuerysetEqual(services_tab._tables['services'].data, - ['', - '', - '', - '', - '', - '', - '', - '', - '']) - - quotas_tab = res.context['tab_group'].get_tab('quotas') - self.assertQuerysetEqual(quotas_tab._tables['quotas'].data, - ['', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - ''], - ordered=False) - zones_tab = res.context['tab_group'].get_tab('zones') self.assertQuerysetEqual(zones_tab._tables['zones'].data, ['']) diff --git a/openstack_dashboard/usage/quotas.py b/openstack_dashboard/usage/quotas.py index 611f54c2bc..2df147e3ac 100644 --- a/openstack_dashboard/usage/quotas.py +++ b/openstack_dashboard/usage/quotas.py @@ -26,6 +26,9 @@ NOVA_QUOTA_FIELDS = ("metadata_items", "security_groups", "security_group_rules",) +MISSING_QUOTA_FIELDS = ("key_pairs", + "injected_file_path_bytes",) + CINDER_QUOTA_FIELDS = ("volumes", "snapshots", "gigabytes",) diff --git a/requirements.txt b/requirements.txt index eb38ccfe1c..e6ef2e2bbb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ kombu>=2.4.8 lesscpy>=0.9j iso8601>=0.1.4 netaddr -python-cinderclient>=1.0.4 +python-cinderclient>=1.0.5 python-glanceclient>=0.9.0 python-heatclient>=0.2.3 python-keystoneclient>=0.3.0