diff --git a/openstack_dashboard/api/cinder.py b/openstack_dashboard/api/cinder.py index 515281d078..734bacaa92 100644 --- a/openstack_dashboard/api/cinder.py +++ b/openstack_dashboard/api/cinder.py @@ -393,6 +393,10 @@ def volume_type_list_with_qos_associations(request): return vol_types +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 b640f6a915..66df528958 100644 --- a/openstack_dashboard/api/nova.py +++ b/openstack_dashboard/api/nova.py @@ -670,6 +670,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/conf/cinder_policy.json b/openstack_dashboard/conf/cinder_policy.json index 8fe7bc7718..b20bb853df 100644 --- a/openstack_dashboard/conf/cinder_policy.json +++ b/openstack_dashboard/conf/cinder_policy.json @@ -31,6 +31,7 @@ "volume_extension:quotas:show": [], "volume_extension:quotas:update": [["rule:admin_api"]], + "volume_extension:quota_classes": [], "volume_extension:volume_admin_actions:reset_status": [["rule:admin_api"]], "volume_extension:snapshot_admin_actions:reset_status": [["rule:admin_api"]], diff --git a/openstack_dashboard/conf/nova_policy.json b/openstack_dashboard/conf/nova_policy.json index f53c1b2582..487e46c118 100644 --- a/openstack_dashboard/conf/nova_policy.json +++ b/openstack_dashboard/conf/nova_policy.json @@ -166,6 +166,8 @@ "compute_extension:v3:os-quota-sets:show": "", "compute_extension:v3:os-quota-sets:update": "rule:admin_api", "compute_extension:v3:os-quota-sets:delete": "rule:admin_api", + "compute_extension:quota_classes": "", + "compute_extension:v3:os-quota-class-sets": "", "compute_extension:rescue": "", "compute_extension:v3:os-rescue": "", "compute_extension:security_group_default_rules": "rule:admin_api", diff --git a/openstack_dashboard/dashboards/admin/dashboard.py b/openstack_dashboard/dashboards/admin/dashboard.py index 25d198f956..d594fabddd 100644 --- a/openstack_dashboard/dashboards/admin/dashboard.py +++ b/openstack_dashboard/dashboards/admin/dashboard.py @@ -22,7 +22,7 @@ class SystemPanels(horizon.PanelGroup): name = _("System") panels = ('overview', 'metering', 'hypervisors', 'aggregates', 'instances', 'volumes', 'flavors', 'images', - 'networks', 'routers', 'info') + 'networks', 'routers', 'defaults', 'info') class Admin(horizon.Dashboard): 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..56fe532acb --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/panel.py @@ -0,0 +1,27 @@ +# 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 _ + +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..9294b75ad6 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/tables.py @@ -0,0 +1,76 @@ +# 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 _ + +from horizon import tables + + +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 = { + 'injected_file_content_bytes': _('Injected File Content Bytes'), + 'injected_file_path_bytes': _('Length of Injected File Path'), + 'metadata_items': _('Metadata Items'), + 'cores': _('VCPUs'), + 'instances': _('Instances'), + 'injected_files': _('Injected Files'), + 'volumes': _('Volumes'), + 'snapshots': _('Volume Snapshots'), + 'gigabytes': _('Total Size of Volumes and Snapshots (GB)'), + 'ram': _('RAM (MB)'), + 'floating_ips': _('Floating IPs'), + 'security_groups': _('Security Groups'), + 'security_group_rules': _('Security Group Rules'), + 'key_pairs': _('Key Pairs'), + 'fixed_ips': _('Fixed IPs'), + 'volumes_volume_luks': _('LUKS Volumes'), + 'snapshots_volume_luks': _('LUKS Volume Snapshots'), + 'gigabytes_volume_luks': + _('Total Size of LUKS Volumes and Snapshots (GB)'), + 'dm-crypt': _('dm-crypt'), + } + 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..832a09a0b4 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/tabs.py @@ -0,0 +1,44 @@ +# 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 _ + +from horizon import exceptions +from horizon import tabs + +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: + data = quotas.get_default_quota_data(request) + 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..c855030374 --- /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..b1faa88eb9 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/tests.py @@ -0,0 +1,137 @@ +# 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 +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._test_index(neutron_enabled=True) + + def test_index_with_neutron_disabled(self): + self._test_index(neutron_enabled=False) + + def test_index_with_neutron_sg_disabled(self): + self._test_index(neutron_enabled=True, + neutron_sg_enabled=False) + + def _test_index(self, neutron_enabled=True, neutron_sg_enabled=True): + # Neutron does not have an API for getting default system + # quotas. When not using Neutron, the floating ips quotas + # should be in the list. + self.mox.StubOutWithMock(api.nova, 'default_quota_get') + self.mox.StubOutWithMock(api.cinder, 'default_quota_get') + self.mox.StubOutWithMock(api.base, 'is_service_enabled') + if neutron_enabled: + self.mox.StubOutWithMock(api.neutron, 'is_extension_supported') + + api.base.is_service_enabled(IsA(http.HttpRequest), 'volume') \ + .AndReturn(True) + api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \ + .MultipleTimes().AndReturn(neutron_enabled) + + 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()) + if neutron_enabled: + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'security-group').AndReturn(neutron_sg_enabled) + + 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') + expected_tabs = ['', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + ''] + if neutron_enabled: + expected_tabs.remove('') + expected_tabs.remove('') + if neutron_sg_enabled: + expected_tabs.remove('') + expected_tabs.remove('') + + self.assertQuerysetEqual(quotas_tab._tables['quotas'].data, + expected_tabs, + 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', + 'get_disabled_quotas')}) + def test_update_default_quotas(self): + quota = self.quotas.first() + + # init + quotas.get_disabled_quotas(IsA(http.HttpRequest)) \ + .AndReturn(self.disabled_quotas.first()) + 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..3201642c3d --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/urls.py @@ -0,0 +1,24 @@ +# 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 import patterns +from django.conf.urls import url + +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..787dc979f5 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/views.py @@ -0,0 +1,47 @@ +# 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 _ + +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 + + +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..a416d77a8c --- /dev/null +++ b/openstack_dashboard/dashboards/admin/defaults/workflows.py @@ -0,0 +1,100 @@ +# 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 _ + +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 = _("Length of Injected File Path") + injected_file_content_bytes = forms.IntegerField(min_value=-1, + label=ifcb_label) + metadata_items = forms.IntegerField(min_value=-1, + label=_("Metadata Items")) + ram = forms.IntegerField(min_value=-1, label=_("RAM (MB)")) + floating_ips = forms.IntegerField(min_value=-1, label=_("Floating IPs")) + key_pairs = forms.IntegerField(min_value=-1, label=_("Key Pairs")) + injected_file_path_bytes = forms.IntegerField(min_value=-1, + label=ifpb_label) + instances = forms.IntegerField(min_value=-1, label=_("Instances")) + security_group_rules = forms.IntegerField(min_value=-1, + label=_("Security Group Rules")) + injected_files = forms.IntegerField(min_value=-1, + label=_("Injected Files")) + cores = forms.IntegerField(min_value=-1, label=_("VCPUs")) + security_groups = forms.IntegerField(min_value=-1, + label=_("Security Groups")) + gigabytes = forms.IntegerField(min_value=-1, + label=_("Total Size of Volumes and Snapshots (GB)")) + snapshots = forms.IntegerField(min_value=-1, label=_("Volume Snapshots")) + volumes = forms.IntegerField(min_value=-1, label=_("Volumes")) + + 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 UpdateDefaultQuotasStep(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.') + failure_message = _('Unable to update default quotas.') + success_url = "horizon:admin:defaults:index" + default_steps = (UpdateDefaultQuotasStep,) + + 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/tables.py b/openstack_dashboard/dashboards/admin/info/tables.py index 2bec2e2943..c654d9b5a8 100644 --- a/openstack_dashboard/dashboards/admin/info/tables.py +++ b/openstack_dashboard/dashboards/admin/info/tables.py @@ -186,55 +186,3 @@ class NetworkAgentsTable(tables.DataTable): verbose_name = _("Network Agents") table_actions = (NetworkAgentsFilterAction,) multi_select = False - - -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): - QUOTA_NAMES = { - 'injected_file_content_bytes': _('Injected File Content Bytes'), - 'injected_file_path_bytes': _('Length of Injected File Path'), - 'metadata_items': _('Metadata Items'), - 'cores': _('VCPUs'), - 'instances': _('Instances'), - 'injected_files': _('Injected Files'), - 'volumes': _('Volumes'), - 'snapshots': _('Volume Snapshots'), - 'gigabytes': _('Total Size of Volumes and Snapshots (GB)'), - 'ram': _('RAM (MB)'), - 'floating_ips': _('Floating IPs'), - 'security_groups': _('Security Groups'), - 'security_group_rules': _('Security Group Rules'), - 'key_pairs': _('Key Pairs'), - 'fixed_ips': _('Fixed IPs'), - 'volumes_volume_luks': _('LUKS Volumes'), - 'snapshots_volume_luks': _('LUKS Volume Snapshots'), - 'gigabytes_volume_luks': - _('Total Size of LUKS Volumes and Snapshots (GB)'), - 'dm-crypt': _('dm-crypt'), - } - 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,) - multi_select = False diff --git a/openstack_dashboard/dashboards/admin/info/tabs.py b/openstack_dashboard/dashboards/admin/info/tabs.py index 3cf84d6140..d2d8d34377 100644 --- a/openstack_dashboard/dashboards/admin/info/tabs.py +++ b/openstack_dashboard/dashboards/admin/info/tabs.py @@ -23,7 +23,6 @@ from openstack_dashboard.api import neutron from openstack_dashboard.api import nova from openstack_dashboard.dashboards.admin.info import constants from openstack_dashboard.dashboards.admin.info import tables -from openstack_dashboard.usage import quotas class ServicesTab(tabs.TableTab): @@ -103,25 +102,8 @@ class NetworkAgentsTab(tabs.TableTab): return agents -class DefaultQuotasTab(tabs.TableTab): - table_classes = (tables.QuotasTable,) - name = _("Default Quotas") - slug = "quotas" - template_name = constants.INFO_DETAIL_TEMPLATE_NAME - permissions = ('openstack.services.compute',) - - def get_quotas_data(self): - request = self.tab_group.request - try: - data = quotas.get_default_quota_data(request) - except Exception: - data = [] - exceptions.handle(self.request, _('Unable to get quota info.')) - return data - - class SystemInfoTabs(tabs.TabGroup): slug = "system_info" tabs = (ServicesTab, NovaServicesTab, CinderServicesTab, - NetworkAgentsTab, DefaultQuotasTab) + NetworkAgentsTab) sticky = True diff --git a/openstack_dashboard/dashboards/admin/info/tests.py b/openstack_dashboard/dashboards/admin/info/tests.py index 6c15a66625..2893785cc1 100644 --- a/openstack_dashboard/dashboards/admin/info/tests.py +++ b/openstack_dashboard/dashboards/admin/info/tests.py @@ -26,31 +26,23 @@ INDEX_URL = reverse('horizon:admin:info:index') class SystemInfoViewTests(test.BaseAdminViewTests): @test.create_stubs({api.base: ('is_service_enabled',), - api.nova: ('default_quota_get', 'service_list'), + api.nova: ('service_list',), api.neutron: ('agent_list', 'is_extension_supported'), - api.cinder: ('default_quota_get', 'service_list')}) + api.cinder: ('service_list',)}) def test_index(self): services = self.services.list() api.nova.service_list(IsA(http.HttpRequest)).AndReturn(services) + api.base.is_service_enabled(IsA(http.HttpRequest), IgnoreArg()) \ + .MultipleTimes().AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'agent').AndReturn(True) agents = self.agents.list() api.neutron.agent_list(IsA(http.HttpRequest)).AndReturn(agents) - api.base.is_service_enabled(IsA(http.HttpRequest), IgnoreArg()) \ - .MultipleTimes().AndReturn(True) - api.nova.default_quota_get(IsA(http.HttpRequest), - IgnoreArg()).AndReturn({}) - - api.cinder.default_quota_get(IsA(http.HttpRequest), self.tenant.id)\ - .AndReturn(self.cinder_quotas.first()) cinder_services = self.cinder_services.list() api.cinder.service_list(IsA(http.HttpRequest)).\ AndReturn(cinder_services) - api.neutron.is_extension_supported(IsA(http.HttpRequest), - 'security-group').AndReturn(True) - self.mox.ReplayAll() res = self.client.get(INDEX_URL) @@ -79,25 +71,19 @@ class SystemInfoViewTests(test.BaseAdminViewTests): self.mox.VerifyAll() @test.create_stubs({api.base: ('is_service_enabled',), - api.cinder: ('default_quota_get', 'service_list'), - api.nova: ('default_quota_get', 'service_list'), + api.cinder: ('service_list',), + api.nova: ('service_list',), api.neutron: ('agent_list', 'is_extension_supported')}) def test_cinder_services_index(self): cinder_services = self.cinder_services.list() api.nova.service_list(IsA(http.HttpRequest)).AndReturn([]) - api.cinder.default_quota_get(IsA(http.HttpRequest), self.tenant.id)\ - .AndReturn(self.cinder_quotas.first()) api.cinder.service_list(IsA(http.HttpRequest)).\ AndReturn(cinder_services) api.neutron.agent_list(IsA(http.HttpRequest)).AndReturn([]) api.base.is_service_enabled(IsA(http.HttpRequest), IgnoreArg()) \ .MultipleTimes().AndReturn(True) - api.nova.default_quota_get(IsA(http.HttpRequest), - IgnoreArg()).AndReturn({}) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'agent').AndReturn(True) - api.neutron.is_extension_supported(IsA(http.HttpRequest), - 'security-group').AndReturn(True) self.mox.ReplayAll() res = self.client.get(INDEX_URL) @@ -109,73 +95,3 @@ class SystemInfoViewTests(test.BaseAdminViewTests): ['cinder_services'].data, ['', '']) - - def test_default_quotas_index(self): - self._test_default_quotas_index(neutron_enabled=True) - - def test_default_quotas_index_with_neutron_disabled(self): - self._test_default_quotas_index(neutron_enabled=False) - - def test_default_quotas_index_with_neutron_sg_disabled(self): - self._test_default_quotas_index(neutron_enabled=True, - neutron_sg_enabled=False) - - @test.create_stubs({api.base: ('is_service_enabled',), - api.nova: ('default_quota_get', 'service_list'), - api.cinder: ('default_quota_get', 'service_list')}) - def _test_default_quotas_index(self, neutron_enabled=True, - neutron_sg_enabled=True): - # 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') \ - .MultipleTimes().AndReturn(True) - api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \ - .MultipleTimes().AndReturn(neutron_enabled) - - api.nova.service_list(IsA(http.HttpRequest)).AndReturn([]) - 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()) - api.cinder.service_list(IsA(http.HttpRequest)).AndReturn([]) - - if neutron_enabled: - self.mox.StubOutWithMock(api.neutron, 'agent_list') - self.mox.StubOutWithMock(api.neutron, 'is_extension_supported') - api.neutron.is_extension_supported(IsA(http.HttpRequest), - 'agent').AndReturn(True) - - api.neutron.agent_list(IsA(http.HttpRequest)).AndReturn([]) - - api.neutron.is_extension_supported(IsA(http.HttpRequest), - 'security-group').AndReturn(neutron_sg_enabled) - - self.mox.ReplayAll() - - res = self.client.get(INDEX_URL) - - quotas_tab = res.context['tab_group'].get_tab('quotas') - expected_tabs = ['', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - ''] - if neutron_enabled: - expected_tabs.remove('') - expected_tabs.remove('') - if neutron_sg_enabled: - expected_tabs.remove('') - expected_tabs.remove('') - - self.assertQuerysetEqual(quotas_tab._tables['quotas'].data, - expected_tabs, - ordered=False) diff --git a/openstack_dashboard/enabled/_70_admin_default_panel.py.example b/openstack_dashboard/enabled/_70_admin_default_panel.py.example index 23d3773ab1..b38ebdaf89 100644 --- a/openstack_dashboard/enabled/_70_admin_default_panel.py.example +++ b/openstack_dashboard/enabled/_70_admin_default_panel.py.example @@ -1,9 +1,9 @@ # The name of the panel to be added to HORIZON_CONFIG. Required. -PANEL = 'instances' +PANEL = 'defaults' # The name of the dashboard the PANEL associated with. Required. PANEL_DASHBOARD = 'admin' # The name of the panel group the PANEL is associated with. PANEL_GROUP = 'admin' # If set, it will update the default panel of the PANEL_DASHBOARD. -DEFAULT_PANEL = 'instances' +DEFAULT_PANEL = 'defaults' diff --git a/openstack_dashboard/test/test_plugins/panel_config/_30_admin_default_panel.py b/openstack_dashboard/test/test_plugins/panel_config/_30_admin_default_panel.py index 23d3773ab1..b38ebdaf89 100644 --- a/openstack_dashboard/test/test_plugins/panel_config/_30_admin_default_panel.py +++ b/openstack_dashboard/test/test_plugins/panel_config/_30_admin_default_panel.py @@ -1,9 +1,9 @@ # The name of the panel to be added to HORIZON_CONFIG. Required. -PANEL = 'instances' +PANEL = 'defaults' # The name of the dashboard the PANEL associated with. Required. PANEL_DASHBOARD = 'admin' # The name of the panel group the PANEL is associated with. PANEL_GROUP = 'admin' # If set, it will update the default panel of the PANEL_DASHBOARD. -DEFAULT_PANEL = 'instances' +DEFAULT_PANEL = 'defaults' diff --git a/openstack_dashboard/test/test_plugins/panel_tests.py b/openstack_dashboard/test/test_plugins/panel_tests.py index c68573b2df..7f8c6b4a69 100644 --- a/openstack_dashboard/test/test_plugins/panel_tests.py +++ b/openstack_dashboard/test/test_plugins/panel_tests.py @@ -48,4 +48,4 @@ class PanelPluginTests(test.PluginTestCase): def test_default_panel(self): dashboard = horizon.get_dashboard("admin") - self.assertEqual('instances', dashboard.default_panel) + self.assertEqual('defaults', dashboard.default_panel)