Reworks quotas and services panels into System Info panel.

This is a step towards combining all the "read only" data about
an OpenStack cloud into one place for admin inspection.

Implements blueprint system-info-panel.

Change-Id: I2dc52a855c1c8a488fb9c66b82e0faca765b3812
This commit is contained in:
Gabriel Hurley 2012-11-17 15:49:17 -08:00
parent 0e328995ec
commit 2f98befe3f
20 changed files with 196 additions and 234 deletions

View File

@ -183,7 +183,8 @@ def novaclient(request):
request.user.token.id,
project_id=request.user.tenant_id,
auth_url=url_for(request, 'compute'),
insecure=insecure)
insecure=insecure,
http_log_debug=settings.DEBUG)
c.client.auth_token = request.user.token.id
c.client.management_url = url_for(request, 'compute')
return c

View File

@ -22,8 +22,8 @@ import horizon
class SystemPanels(horizon.PanelGroup):
slug = "admin"
name = _("System Panel")
panels = ('overview', 'instances', 'volumes', 'services', 'flavors',
'images', 'projects', 'users', 'quotas', 'networks',)
panels = ('overview', 'instances', 'volumes', 'flavors',
'images', 'projects', 'users', 'networks', 'info')
class Admin(horizon.Dashboard):

View File

@ -26,8 +26,8 @@ from openstack_dashboard.dashboards.admin import dashboard
class Quotas(horizon.Panel):
name = _("Quotas")
slug = 'quotas'
name = _("System Info")
slug = 'info'
dashboard.Admin.register(Quotas)

View File

@ -1,6 +1,5 @@
import logging
from django import template
from django.utils.translation import ugettext_lazy as _
from horizon import tables
@ -9,6 +8,36 @@ from horizon import tables
LOG = logging.getLogger(__name__)
class QuotaFilterAction(tables.FilterAction):
def filter(self, table, tenants, filter_string):
q = filter_string.lower()
def comp(tenant):
if q in tenant.name.lower():
return True
return False
return filter(comp, tenants)
def get_quota_name(quota):
return quota.name.replace("_", " ").title()
class QuotasTable(tables.DataTable):
name = tables.Column(get_quota_name, verbose_name=_('Quota Name'))
limit = tables.Column("limit", verbose_name=_('Limit'))
def get_object_id(self, obj):
return obj.name
class Meta:
name = "quotas"
verbose_name = _("Quotas")
table_actions = (QuotaFilterAction,)
multi_select = False
class ServiceFilterAction(tables.FilterAction):
def filter(self, table, services, filter_string):
q = filter_string.lower()

View File

@ -0,0 +1,62 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, 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.api import keystone
from openstack_dashboard.usage import quotas
from .tables import QuotasTable, ServicesTable
class DefaultQuotasTab(tabs.TableTab):
table_classes = (QuotasTable,)
name = _("Default Quotas")
slug = "quotas"
template_name = ("horizon/common/_detail_table.html")
def get_quotas_data(self):
request = self.tab_group.request
try:
quota_set = quotas.get_default_quota_data(request)
data = quota_set.items
except:
data = []
exceptions.handle(self.request, _('Unable to get quota info.'))
return data
class ServicesTab(tabs.TableTab):
table_classes = (ServicesTable,)
name = _("Services")
slug = "services"
template_name = ("horizon/common/_detail_table.html")
def get_services_data(self):
request = self.tab_group.request
services = []
for i, service in enumerate(request.user.service_catalog):
service['id'] = i
services.append(keystone.Service(service))
return services
class SystemInfoTabs(tabs.TabGroup):
slug = "system_info"
tabs = (ServicesTab, DefaultQuotasTab,)
sticky = True

View File

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "System Info" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("System Info")%}
{% endblock page_header %}
{% block main %}
<div class="row-fluid">
<div class="span12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,71 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, 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 import http
from django.core.urlresolvers import reverse
from mox import IsA
from openstack_dashboard.test import helpers as test
from openstack_dashboard import api
INDEX_URL = reverse('horizon:admin:info:index')
class ServicessViewTests(test.BaseAdminViewTests):
def test_index(self):
self.mox.StubOutWithMock(api.nova, 'default_quota_get')
self.mox.StubOutWithMock(api.cinder, 'default_quota_get')
api.nova.default_quota_get(IsA(http.HttpRequest),
self.tenant.id).AndReturn(self.quotas.nova)
api.cinder.default_quota_get(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(self.quotas.nova)
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'admin/info/index.html')
services_tab = res.context['tab_group'].get_tab('services')
self.assertQuerysetEqual(services_tab._tables['services'].data,
['<Service: compute>',
'<Service: volume>',
'<Service: image>',
'<Service: identity (native backend)>',
'<Service: object-store>',
'<Service: network>',
'<Service: ec2>'])
quotas_tab = res.context['tab_group'].get_tab('quotas')
self.assertQuerysetEqual(quotas_tab._tables['quotas'].data,
['<Quota: (injected_file_content_bytes, 1)>',
'<Quota: (metadata_items, 1)>',
'<Quota: (injected_files, 1)>',
'<Quota: (gigabytes, 1000)>',
'<Quota: (ram, 10000)>',
'<Quota: (floating_ips, 1)>',
'<Quota: (instances, 10)>',
'<Quota: (volumes, 1)>',
'<Quota: (cores, 10)>',
'<Quota: (injected_file_content_bytes, 1)>',
'<Quota: (metadata_items, 1)>',
'<Quota: (injected_files, 1)>',
'<Quota: (gigabytes, 1000)>',
'<Quota: (ram, 10000)>',
'<Quota: (floating_ips, 1)>',
'<Quota: (instances, 10)>',
'<Quota: (volumes, 1)>',
'<Quota: (cores, 10)>'])

View File

@ -23,5 +23,5 @@ from django.conf.urls.defaults import patterns, url
from .views import IndexView
urlpatterns = patterns('openstack_dashboard.dashboards.admin.quotas.views',
urlpatterns = patterns('openstack_dashboard.dashboards.admin.info.views',
url(r'^$', IndexView.as_view(), name='index'))

View File

@ -18,10 +18,16 @@
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls.defaults import patterns, url
import logging
from .views import IndexView
from horizon import tabs
from .tabs import SystemInfoTabs
urlpatterns = patterns('openstack_dashboard.dashboards.admin.services.views',
url(r'^$', IndexView.as_view(), name='index'))
LOG = logging.getLogger(__name__)
class IndexView(tabs.TabbedTableView):
tab_group_class = SystemInfoTabs
template_name = 'admin/info/index.html'

View File

@ -1,38 +0,0 @@
import logging
from django.utils.translation import ugettext_lazy as _
from horizon import tables
LOG = logging.getLogger(__name__)
class QuotaFilterAction(tables.FilterAction):
def filter(self, table, tenants, filter_string):
q = filter_string.lower()
def comp(tenant):
if q in tenant.name.lower():
return True
return False
return filter(comp, tenants)
def get_quota_name(quota):
return quota.name.replace("_", " ").title()
class QuotasTable(tables.DataTable):
name = tables.Column(get_quota_name, verbose_name=_('Quota Name'))
limit = tables.Column("limit", verbose_name=_('Limit'))
def get_object_id(self, obj):
return obj.name
class Meta:
name = "quotas"
verbose_name = _("Quotas")
table_actions = (QuotaFilterAction,)
multi_select = False

View File

@ -1,13 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Quotas{% endblock %}
{% block page_header %}
{% url horizon:admin:quotas:index as refresh_link %}
{# to make searchable false, just remove it from the include statement #}
{% include "horizon/common/_page_header.html" with title=_("Default Quotas") refresh_link=refresh_link searchable="true" %}
{% endblock page_header %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -1 +0,0 @@
#FIXME(gabriel): Needs tests!

View File

@ -1,47 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.utils.translation import ugettext as _
from horizon import exceptions
from horizon import tables
from openstack_dashboard import api
from .tables import QuotasTable
LOG = logging.getLogger(__name__)
class IndexView(tables.DataTableView):
table_class = QuotasTable
template_name = 'admin/quotas/index.html'
def get_data(self):
try:
quota_set = api.default_quota_get(self.request,
self.request.user.tenant_id)
data = quota_set.items
except:
data = []
exceptions.handle(self.request, _('Unable to get quota info.'))
return data

View File

@ -1,33 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, 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 Services(horizon.Panel):
name = _("Services")
slug = 'services'
dashboard.Admin.register(Services)

View File

@ -1,14 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Services{% endblock %}
{% block page_header %}
{% url horizon:admin:services:index as refresh_link %}
{# to make searchable false, just remove it from the include statement #}
{% include "horizon/common/_page_header.html" with title=_("Services") refresh_link=refresh_link searchable="true" %}
{% endblock page_header %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -1,36 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, 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 openstack_dashboard.test import helpers as test
INDEX_URL = reverse('horizon:admin:services:index')
class ServicessViewTests(test.BaseAdminViewTests):
def test_index(self):
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'admin/services/index.html')
self.assertQuerysetEqual(res.context['table'].data,
['<Service: compute>',
'<Service: volume>',
'<Service: image>',
'<Service: identity (native backend)>',
'<Service: object-store>',
'<Service: network>',
'<Service: ec2>'])

View File

@ -1,41 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from horizon import tables
from openstack_dashboard import api
from .tables import ServicesTable
LOG = logging.getLogger(__name__)
class IndexView(tables.DataTableView):
table_class = ServicesTable
template_name = 'admin/services/index.html'
def get_data(self):
services = []
for i, service in enumerate(self.request.user.service_catalog):
service['id'] = i
services.append(api.keystone.Service(service))
return services

View File

@ -280,6 +280,7 @@ def data(TEST):
injected_files='1',
cores='10')
quota = quotas.QuotaSet(quotas.QuotaSetManager(None), quota_data)
TEST.quotas.nova = QuotaSetWrapper(quota)
TEST.quotas.add(QuotaSetWrapper(quota))
# Quota Usages