Merge "Remove the update default quotas feature"
This commit is contained in:
commit
9e9a41b4b9
@ -600,7 +600,7 @@ Examples
|
|||||||
--------
|
--------
|
||||||
|
|
||||||
To add a new panel to the Admin panel group in Admin dashboard, create a file
|
To add a new panel to the Admin panel group in Admin dashboard, create a file
|
||||||
``openstack_dashboard/local/enabled/_60_admin_add_panel.py`` with the follwing
|
``openstack_dashboard/local/enabled/_60_admin_add_panel.py`` with the following
|
||||||
content::
|
content::
|
||||||
|
|
||||||
PANEL = 'plugin_panel'
|
PANEL = 'plugin_panel'
|
||||||
@ -617,11 +617,11 @@ the following content::
|
|||||||
PANEL_GROUP = 'admin'
|
PANEL_GROUP = 'admin'
|
||||||
REMOVE_PANEL = True
|
REMOVE_PANEL = True
|
||||||
|
|
||||||
To change the default panel of Admin dashboard to Defaults panel, create a file
|
To change the default panel of Admin dashboard to Instances panel, create a file
|
||||||
``openstack_dashboard/local/enabled/_80_admin_default_panel.py`` with the
|
``openstack_dashboard/local/enabled/_80_admin_default_panel.py`` with the
|
||||||
following content::
|
following content::
|
||||||
|
|
||||||
PANEL = 'defaults'
|
PANEL = 'instances'
|
||||||
PANEL_DASHBOARD = 'admin'
|
PANEL_DASHBOARD = 'admin'
|
||||||
PANEL_GROUP = 'admin'
|
PANEL_GROUP = 'admin'
|
||||||
DEFAULT_PANEL = 'defaults'
|
DEFAULT_PANEL = 'instances'
|
||||||
|
@ -237,10 +237,6 @@ def default_quota_get(request, tenant_id):
|
|||||||
return base.QuotaSet(cinderclient(request).quotas.defaults(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):
|
def volume_type_list(request):
|
||||||
return cinderclient(request).volume_types.list()
|
return cinderclient(request).volume_types.list()
|
||||||
|
|
||||||
|
@ -622,10 +622,6 @@ def default_quota_get(request, tenant_id):
|
|||||||
return base.QuotaSet(novaclient(request).quotas.defaults(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):
|
def usage_get(request, tenant_id, start, end):
|
||||||
return NovaUsage(novaclient(request).usage.get(tenant_id, start, end))
|
return NovaUsage(novaclient(request).usage.get(tenant_id, start, end))
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
|
|
||||||
"volume_extension:quotas:show": [],
|
"volume_extension:quotas:show": [],
|
||||||
"volume_extension:quotas:update": [["rule:admin_api"]],
|
"volume_extension:quotas:update": [["rule:admin_api"]],
|
||||||
"volume_extension:quota_classes": [],
|
|
||||||
|
|
||||||
"volume_extension:volume_admin_actions:reset_status": [["rule:admin_api"]],
|
"volume_extension:volume_admin_actions:reset_status": [["rule:admin_api"]],
|
||||||
"volume_extension:snapshot_admin_actions:reset_status": [["rule:admin_api"]],
|
"volume_extension:snapshot_admin_actions:reset_status": [["rule:admin_api"]],
|
||||||
|
@ -166,8 +166,6 @@
|
|||||||
"compute_extension:v3:os-quota-sets:show": "",
|
"compute_extension:v3:os-quota-sets:show": "",
|
||||||
"compute_extension:v3:os-quota-sets:update": "rule:admin_api",
|
"compute_extension:v3:os-quota-sets:update": "rule:admin_api",
|
||||||
"compute_extension:v3:os-quota-sets:delete": "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:rescue": "",
|
||||||
"compute_extension:v3:os-rescue": "",
|
"compute_extension:v3:os-rescue": "",
|
||||||
"compute_extension:security_group_default_rules": "rule:admin_api",
|
"compute_extension:security_group_default_rules": "rule:admin_api",
|
||||||
|
@ -24,7 +24,7 @@ class SystemPanels(horizon.PanelGroup):
|
|||||||
name = _("System Panel")
|
name = _("System Panel")
|
||||||
panels = ('overview', 'metering', 'hypervisors', 'aggregates',
|
panels = ('overview', 'metering', 'hypervisors', 'aggregates',
|
||||||
'instances', 'volumes', 'flavors', 'images',
|
'instances', 'volumes', 'flavors', 'images',
|
||||||
'networks', 'routers', 'defaults', 'info')
|
'networks', 'routers', 'info')
|
||||||
|
|
||||||
|
|
||||||
class IdentityPanels(horizon.PanelGroup):
|
class IdentityPanels(horizon.PanelGroup):
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
# 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 _
|
|
||||||
|
|
||||||
import horizon
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.admin import dashboard
|
|
||||||
|
|
||||||
|
|
||||||
class Defaults(horizon.Panel):
|
|
||||||
name = _("Defaults")
|
|
||||||
slug = 'defaults'
|
|
||||||
|
|
||||||
|
|
||||||
dashboard.Admin.register(Defaults)
|
|
@ -1,77 +0,0 @@
|
|||||||
# 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 _
|
|
||||||
|
|
||||||
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': _('Injected File Path Bytes'),
|
|
||||||
'metadata_items': _('Metadata Items'),
|
|
||||||
'cores': _('VCPUs'),
|
|
||||||
'instances': _('Instances'),
|
|
||||||
'injected_files': _('Injected Files'),
|
|
||||||
'volumes': _('Volumes'),
|
|
||||||
'snapshots': _('Snapshots'),
|
|
||||||
'gigabytes': _('Gigabytes'),
|
|
||||||
'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 Volumes Snapshots'),
|
|
||||||
'gigabytes_volume_luks': _('LUKS Volumes Size (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
|
|
@ -1,46 +0,0 @@
|
|||||||
# 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 _
|
|
||||||
|
|
||||||
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
|
|
@ -1,15 +0,0 @@
|
|||||||
{% 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-fluid">
|
|
||||||
<div class="span12">
|
|
||||||
{{ tab_group.render }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,139 +0,0 @@
|
|||||||
# 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
|
|
||||||
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)
|
|
@ -1,26 +0,0 @@
|
|||||||
# 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 import patterns # noqa
|
|
||||||
from django.conf.urls 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'))
|
|
@ -1,49 +0,0 @@
|
|||||||
# 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 _
|
|
||||||
|
|
||||||
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
|
|
@ -1,101 +0,0 @@
|
|||||||
# 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 _
|
|
||||||
|
|
||||||
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")
|
|
||||||
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=_("Gigabytes"))
|
|
||||||
snapshots = forms.IntegerField(min_value=-1, label=_("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
|
|
@ -144,3 +144,54 @@ class NetworkAgentsTable(tables.DataTable):
|
|||||||
verbose_name = _("Network Agents")
|
verbose_name = _("Network Agents")
|
||||||
table_actions = (NetworkAgentsFilterAction,)
|
table_actions = (NetworkAgentsFilterAction,)
|
||||||
multi_select = False
|
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': _('Injected File Path Bytes'),
|
||||||
|
'metadata_items': _('Metadata Items'),
|
||||||
|
'cores': _('VCPUs'),
|
||||||
|
'instances': _('Instances'),
|
||||||
|
'injected_files': _('Injected Files'),
|
||||||
|
'volumes': _('Volumes'),
|
||||||
|
'snapshots': _('Snapshots'),
|
||||||
|
'gigabytes': _('Gigabytes'),
|
||||||
|
'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 Volumes Snapshots'),
|
||||||
|
'gigabytes_volume_luks': _('LUKS Volumes Size (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
|
||||||
|
@ -21,6 +21,7 @@ from openstack_dashboard.api import base
|
|||||||
from openstack_dashboard.api import keystone
|
from openstack_dashboard.api import keystone
|
||||||
from openstack_dashboard.api import neutron
|
from openstack_dashboard.api import neutron
|
||||||
from openstack_dashboard.api import nova
|
from openstack_dashboard.api import nova
|
||||||
|
from openstack_dashboard.usage import quotas
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.admin.info import constants
|
from openstack_dashboard.dashboards.admin.info import constants
|
||||||
from openstack_dashboard.dashboards.admin.info import tables
|
from openstack_dashboard.dashboards.admin.info import tables
|
||||||
@ -79,8 +80,24 @@ class NetworkAgentsTab(tabs.TableTab):
|
|||||||
return agents
|
return agents
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultQuotasTab(tabs.TableTab):
|
||||||
|
table_classes = (tables.QuotasTable,)
|
||||||
|
name = _("Default Quotas")
|
||||||
|
slug = "quotas"
|
||||||
|
template_name = constants.INFO_DETAIL_TEMPLATE_NAME
|
||||||
|
|
||||||
|
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):
|
class SystemInfoTabs(tabs.TabGroup):
|
||||||
slug = "system_info"
|
slug = "system_info"
|
||||||
tabs = (ServicesTab, NovaServicesTab,
|
tabs = (ServicesTab, NovaServicesTab,
|
||||||
NetworkAgentsTab)
|
NetworkAgentsTab, DefaultQuotasTab)
|
||||||
sticky = True
|
sticky = True
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django import http
|
from django import http
|
||||||
|
from mox import IgnoreArg # noqa
|
||||||
from mox import IsA # noqa
|
from mox import IsA # noqa
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
@ -26,14 +27,22 @@ INDEX_URL = reverse('horizon:admin:info:index')
|
|||||||
|
|
||||||
class SystemInfoViewTests(test.BaseAdminViewTests):
|
class SystemInfoViewTests(test.BaseAdminViewTests):
|
||||||
|
|
||||||
@test.create_stubs({api.nova: ('service_list',),
|
@test.create_stubs({api.base: ('is_service_enabled',),
|
||||||
api.neutron: ('agent_list',)})
|
api.nova: ('default_quota_get', 'service_list'),
|
||||||
|
api.neutron: ('agent_list', 'is_extension_supported')})
|
||||||
def test_index(self):
|
def test_index(self):
|
||||||
services = self.services.list()
|
services = self.services.list()
|
||||||
api.nova.service_list(IsA(http.HttpRequest)).AndReturn(services)
|
api.nova.service_list(IsA(http.HttpRequest)).AndReturn(services)
|
||||||
agents = self.agents.list()
|
agents = self.agents.list()
|
||||||
api.neutron.agent_list(IsA(http.HttpRequest)).AndReturn(agents)
|
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.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||||
|
'security-group').AndReturn(True)
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
res = self.client.get(INDEX_URL)
|
res = self.client.get(INDEX_URL)
|
||||||
@ -58,3 +67,69 @@ class SystemInfoViewTests(test.BaseAdminViewTests):
|
|||||||
network_agents_tab._tables['network_agents'].data,
|
network_agents_tab._tables['network_agents'].data,
|
||||||
[agent.__repr__() for agent in self.agents.list()]
|
[agent.__repr__() for agent in self.agents.list()]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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',)})
|
||||||
|
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())
|
||||||
|
|
||||||
|
if neutron_enabled:
|
||||||
|
self.mox.StubOutWithMock(api.neutron, 'agent_list')
|
||||||
|
api.neutron.agent_list(IsA(http.HttpRequest)).AndReturn([])
|
||||||
|
|
||||||
|
self.mox.StubOutWithMock(api.neutron, 'is_extension_supported')
|
||||||
|
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)
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# The name of the panel to be added to HORIZON_CONFIG. Required.
|
# The name of the panel to be added to HORIZON_CONFIG. Required.
|
||||||
PANEL = 'defaults'
|
PANEL = 'instances'
|
||||||
# The name of the dashboard the PANEL associated with. Required.
|
# The name of the dashboard the PANEL associated with. Required.
|
||||||
PANEL_DASHBOARD = 'admin'
|
PANEL_DASHBOARD = 'admin'
|
||||||
# The name of the panel group the PANEL is associated with.
|
# The name of the panel group the PANEL is associated with.
|
||||||
PANEL_GROUP = 'admin'
|
PANEL_GROUP = 'admin'
|
||||||
|
|
||||||
# If set, it will update the default panel of the PANEL_DASHBOARD.
|
# If set, it will update the default panel of the PANEL_DASHBOARD.
|
||||||
DEFAULT_PANEL = 'defaults'
|
DEFAULT_PANEL = 'instances'
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# The name of the panel to be added to HORIZON_CONFIG. Required.
|
# The name of the panel to be added to HORIZON_CONFIG. Required.
|
||||||
PANEL = 'defaults'
|
PANEL = 'instances'
|
||||||
# The name of the dashboard the PANEL associated with. Required.
|
# The name of the dashboard the PANEL associated with. Required.
|
||||||
PANEL_DASHBOARD = 'admin'
|
PANEL_DASHBOARD = 'admin'
|
||||||
# The name of the panel group the PANEL is associated with.
|
# The name of the panel group the PANEL is associated with.
|
||||||
PANEL_GROUP = 'admin'
|
PANEL_GROUP = 'admin'
|
||||||
|
|
||||||
# If set, it will update the default panel of the PANEL_DASHBOARD.
|
# If set, it will update the default panel of the PANEL_DASHBOARD.
|
||||||
DEFAULT_PANEL = 'defaults'
|
DEFAULT_PANEL = 'instances'
|
||||||
|
@ -83,4 +83,4 @@ class PanelPluginTests(test.TestCase):
|
|||||||
|
|
||||||
def test_default_panel(self):
|
def test_default_panel(self):
|
||||||
dashboard = horizon.get_dashboard("admin")
|
dashboard = horizon.get_dashboard("admin")
|
||||||
self.assertEqual(dashboard.default_panel, 'defaults')
|
self.assertEqual('instances', dashboard.default_panel)
|
||||||
|
Loading…
Reference in New Issue
Block a user