summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2014-10-02 03:08:37 +0000
committerGerrit Code Review <review@openstack.org>2014-10-02 03:08:37 +0000
commitca4a772d5f577791155b7009e03eeede873ef43c (patch)
tree99945d935b70faf3f23368f1ad2e253891c8243b
parent5a44fb44d6f66dafa270b38c127968cb49d0c676 (diff)
parentb2dd9ded59e84ac22381ca2e576c08fc67aafece (diff)
downloadhorizon-ca4a772d5f577791155b7009e03eeede873ef43c.tar.gz
Merge "Revert "Remove the update default quotas feature""
-rw-r--r--openstack_dashboard/api/cinder.py4
-rw-r--r--openstack_dashboard/api/nova.py4
-rw-r--r--openstack_dashboard/conf/cinder_policy.json1
-rw-r--r--openstack_dashboard/conf/nova_policy.json2
-rw-r--r--openstack_dashboard/dashboards/admin/dashboard.py2
-rw-r--r--openstack_dashboard/dashboards/admin/defaults/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/admin/defaults/panel.py27
-rw-r--r--openstack_dashboard/dashboards/admin/defaults/tables.py76
-rw-r--r--openstack_dashboard/dashboards/admin/defaults/tabs.py44
-rw-r--r--openstack_dashboard/dashboards/admin/defaults/templates/defaults/index.html15
-rw-r--r--openstack_dashboard/dashboards/admin/defaults/tests.py137
-rw-r--r--openstack_dashboard/dashboards/admin/defaults/urls.py24
-rw-r--r--openstack_dashboard/dashboards/admin/defaults/views.py47
-rw-r--r--openstack_dashboard/dashboards/admin/defaults/workflows.py100
-rw-r--r--openstack_dashboard/dashboards/admin/info/tables.py52
-rw-r--r--openstack_dashboard/dashboards/admin/info/tabs.py20
-rw-r--r--openstack_dashboard/dashboards/admin/info/tests.py96
-rw-r--r--openstack_dashboard/enabled/_70_admin_default_panel.py.example4
-rw-r--r--openstack_dashboard/test/test_plugins/panel_config/_30_admin_default_panel.py4
-rw-r--r--openstack_dashboard/test/test_plugins/panel_tests.py2
20 files changed, 494 insertions, 167 deletions
diff --git a/openstack_dashboard/api/cinder.py b/openstack_dashboard/api/cinder.py
index 515281d07..734bacaa9 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 b640f6a91..66df52895 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 8fe7bc771..b20bb853d 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 f53c1b258..487e46c11 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 25d198f95..d594fabdd 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 000000000..e69de29bb
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/defaults/__init__.py
diff --git a/openstack_dashboard/dashboards/admin/defaults/panel.py b/openstack_dashboard/dashboards/admin/defaults/panel.py
new file mode 100644
index 000000000..56fe532ac
--- /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 000000000..9294b75ad
--- /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 000000000..832a09a0b
--- /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 000000000..c85503037
--- /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 %}
+<div class="row">
+ <div class="col-sm-12">
+ {{ tab_group.render }}
+ </div>
+</div>
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/defaults/tests.py b/openstack_dashboard/dashboards/admin/defaults/tests.py
new file mode 100644
index 000000000..b1faa88eb
--- /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 = ['<Quota: (injected_file_content_bytes, 1)>',
+ '<Quota: (metadata_items, 1)>',
+ '<Quota: (injected_files, 1)>',
+ '<Quota: (gigabytes, 1000)>',
+ '<Quota: (ram, 10000)>',
+ '<Quota: (instances, 10)>',
+ '<Quota: (snapshots, 1)>',
+ '<Quota: (volumes, 1)>',
+ '<Quota: (cores, 10)>',
+ '<Quota: (floating_ips, 1)>',
+ '<Quota: (fixed_ips, 10)>',
+ '<Quota: (security_groups, 10)>',
+ '<Quota: (security_group_rules, 20)>']
+ if neutron_enabled:
+ expected_tabs.remove('<Quota: (floating_ips, 1)>')
+ expected_tabs.remove('<Quota: (fixed_ips, 10)>')
+ if neutron_sg_enabled:
+ expected_tabs.remove('<Quota: (security_groups, 10)>')
+ expected_tabs.remove('<Quota: (security_group_rules, 20)>')
+
+ 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 000000000..3201642c3
--- /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 000000000..787dc979f
--- /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 000000000..a416d77a8
--- /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 2bec2e294..c654d9b5a 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 3cf84d614..d2d8d3437 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 6c15a6662..2893785cc 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,
['<Service: cinder-scheduler>',
'<Service: cinder-volume>'])
-
- 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 = ['<Quota: (injected_file_content_bytes, 1)>',
- '<Quota: (metadata_items, 1)>',
- '<Quota: (injected_files, 1)>',
- '<Quota: (gigabytes, 1000)>',
- '<Quota: (ram, 10000)>',
- '<Quota: (instances, 10)>',
- '<Quota: (snapshots, 1)>',
- '<Quota: (volumes, 1)>',
- '<Quota: (cores, 10)>',
- '<Quota: (floating_ips, 1)>',
- '<Quota: (fixed_ips, 10)>',
- '<Quota: (security_groups, 10)>',
- '<Quota: (security_group_rules, 20)>']
- if neutron_enabled:
- expected_tabs.remove('<Quota: (floating_ips, 1)>')
- expected_tabs.remove('<Quota: (fixed_ips, 10)>')
- if neutron_sg_enabled:
- expected_tabs.remove('<Quota: (security_groups, 10)>')
- expected_tabs.remove('<Quota: (security_group_rules, 20)>')
-
- 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 23d3773ab..b38ebdaf8 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 23d3773ab..b38ebdaf8 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 c68573b2d..7f8c6b4a6 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)