diff --git a/horizon/tables/base.py b/horizon/tables/base.py index 3a00cb6d5a..1fdaf60744 100644 --- a/horizon/tables/base.py +++ b/horizon/tables/base.py @@ -110,6 +110,7 @@ class Column(html.HTMLElement): ('true', True) ('up', True), ('active', True), + ('yes', True), ('on', True), ('none', None), ('unknown', None), @@ -118,6 +119,7 @@ class Column(html.HTMLElement): ('down', False), ('false', False), ('inactive', False), + ('no', False), ('off', False), ) @@ -168,6 +170,12 @@ class Column(html.HTMLElement): is displayed as a link. Example: ``classes=('link-foo', 'link-bar')``. Defaults to ``None``. + + .. attribute:: wrap_list + + Boolean value indicating whether the contents of this cell should be + wrapped in a ```` tag. Useful in conjunction with Django's + ``unordered_list`` template filter. Defaults to ``False``. """ summation_methods = { "sum": sum, @@ -183,6 +191,7 @@ class Column(html.HTMLElement): ('enabled', True), ('true', True), ('up', True), + ('yes', True), ('active', True), ('on', True), ('none', None), @@ -192,6 +201,7 @@ class Column(html.HTMLElement): ('down', False), ('false', False), ('inactive', False), + ('no', False), ('off', False), ) @@ -199,7 +209,7 @@ class Column(html.HTMLElement): link=None, allowed_data_types=[], hidden=False, attrs=None, status=False, status_choices=None, display_choices=None, empty_value=None, filters=None, classes=None, summation=None, - auto=None, truncate=None, link_classes=None): + auto=None, truncate=None, link_classes=None, wrap_list=False): self.classes = list(classes or getattr(self, "classes", [])) super(Column, self).__init__() self.attrs.update(attrs or {}) @@ -228,6 +238,7 @@ class Column(html.HTMLElement): self.filters = filters or [] self.truncate = truncate self.link_classes = link_classes or [] + self.wrap_list = wrap_list if status_choices: self.status_choices = status_choices @@ -542,6 +553,7 @@ class Cell(html.HTMLElement): self.data = data self.column = column self.row = row + self.wrap_list = column.wrap_list def __repr__(self): return '<%s: %s, %s>' % (self.__class__.__name__, diff --git a/horizon/templates/horizon/common/_data_table_row.html b/horizon/templates/horizon/common/_data_table_row.html index 3e0c8b0f82..d6df2ffab5 100644 --- a/horizon/templates/horizon/common/_data_table_row.html +++ b/horizon/templates/horizon/common/_data_table_row.html @@ -1,3 +1,3 @@ - {% for cell in row %}{{ cell.value }}{% endfor %} + {% for cell in row %}{%if cell.wrap_list %}
    {% endif %}{{ cell.value }}{%if cell.wrap_list %}
{% endif %}{% endfor %} diff --git a/openstack_dashboard/dashboards/admin/aggregates/__init__.py b/openstack_dashboard/dashboards/admin/aggregates/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openstack_dashboard/dashboards/admin/aggregates/panel.py b/openstack_dashboard/dashboards/admin/aggregates/panel.py deleted file mode 100644 index a95f83ed78..0000000000 --- a/openstack_dashboard/dashboards/admin/aggregates/panel.py +++ /dev/null @@ -1,29 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2013 B1 Systems GmbH -# -# 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 Aggregates(horizon.Panel): - name = _("Aggregates") - slug = 'aggregates' - permissions = ('openstack.roles.admin',) - - -dashboard.Admin.register(Aggregates) diff --git a/openstack_dashboard/dashboards/admin/aggregates/tables.py b/openstack_dashboard/dashboards/admin/aggregates/tables.py deleted file mode 100644 index 378e1a18cc..0000000000 --- a/openstack_dashboard/dashboards/admin/aggregates/tables.py +++ /dev/null @@ -1,54 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2013 B1 Systems GmbH -# -# 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 import template -from django.utils.translation import ugettext_lazy as _ - -from horizon import tables - -LOG = logging.getLogger(__name__) - - -def get_hosts(aggregate): - template_name = 'admin/aggregates/_aggregate_hosts.html' - context = {"aggregate": aggregate} - return template.loader.render_to_string(template_name, context) - - -def get_metadata(aggregate): - template_name = 'admin/aggregates/_aggregate_metadata.html' - context = {"aggregate": aggregate} - return template.loader.render_to_string(template_name, context) - - -class AdminAggregatesTable(tables.DataTable): - name = tables.Column("name", - verbose_name=_("Name")) - - availability_zone = tables.Column("availability_zone", - verbose_name=_("Availability Zone")) - - hosts = tables.Column(get_hosts, - verbose_name=_("Hosts")) - - metadata = tables.Column(get_metadata, - verbose_name=_("Metadata")) - - class Meta: - name = "aggregates" - verbose_name = _("Aggregates") diff --git a/openstack_dashboard/dashboards/admin/aggregates/templates/aggregates/_aggregate_hosts.html b/openstack_dashboard/dashboards/admin/aggregates/templates/aggregates/_aggregate_hosts.html deleted file mode 100644 index c85c3e90ab..0000000000 --- a/openstack_dashboard/dashboards/admin/aggregates/templates/aggregates/_aggregate_hosts.html +++ /dev/null @@ -1,5 +0,0 @@ -
    -{% for host in aggregate.hosts %} -
  • {{ host }}
  • -{% endfor %} -
diff --git a/openstack_dashboard/dashboards/admin/aggregates/templates/aggregates/_aggregate_metadata.html b/openstack_dashboard/dashboards/admin/aggregates/templates/aggregates/_aggregate_metadata.html deleted file mode 100644 index ab8be7c330..0000000000 --- a/openstack_dashboard/dashboards/admin/aggregates/templates/aggregates/_aggregate_metadata.html +++ /dev/null @@ -1,5 +0,0 @@ -
    -{% for key, value in aggregate.metadata.iteritems %} -
  • {{ key }} = {{ value }}
  • -{% endfor %} -
diff --git a/openstack_dashboard/dashboards/admin/aggregates/templates/aggregates/index.html b/openstack_dashboard/dashboards/admin/aggregates/templates/aggregates/index.html deleted file mode 100644 index 42b7cab7da..0000000000 --- a/openstack_dashboard/dashboards/admin/aggregates/templates/aggregates/index.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{% trans "Aggregates" %}{% endblock %} - -{% block page_header %} -{% include "horizon/common/_page_header.html" with title=_("All Aggregates") %} -{% endblock page_header %} - -{% block main %} -{{ table.render }} -{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/aggregates/tests.py b/openstack_dashboard/dashboards/admin/aggregates/tests.py deleted file mode 100644 index babcd7356d..0000000000 --- a/openstack_dashboard/dashboards/admin/aggregates/tests.py +++ /dev/null @@ -1,34 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2013 B1 Systems GmbH -# -# 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 - -from openstack_dashboard import api -from openstack_dashboard.test import helpers as test - - -class AggregateViewTest(test.BaseAdminViewTests): - @test.create_stubs({api.nova: ('aggregate_list',)}) - def test_index(self): - aggregates = self.aggregates.list() - api.nova.aggregate_list(IsA(http.HttpRequest)).AndReturn(aggregates) - self.mox.ReplayAll() - - res = self.client.get(reverse('horizon:admin:aggregates:index')) - self.assertTemplateUsed(res, 'admin/aggregates/index.html') - self.assertItemsEqual(res.context['table'].data, aggregates) diff --git a/openstack_dashboard/dashboards/admin/aggregates/urls.py b/openstack_dashboard/dashboards/admin/aggregates/urls.py deleted file mode 100644 index f8649a2f9e..0000000000 --- a/openstack_dashboard/dashboards/admin/aggregates/urls.py +++ /dev/null @@ -1,27 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2013 B1 Systems GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from django.conf.urls.defaults import patterns -from django.conf.urls.defaults import url - -from openstack_dashboard.dashboards.admin.aggregates.views \ - import AdminIndexView - - -urlpatterns = patterns( - 'openstack_dashboard.dashboards.admin.aggregates.views', - url(r'^$', AdminIndexView.as_view(), name='index') -) diff --git a/openstack_dashboard/dashboards/admin/aggregates/views.py b/openstack_dashboard/dashboards/admin/aggregates/views.py deleted file mode 100644 index 2d1a48a870..0000000000 --- a/openstack_dashboard/dashboards/admin/aggregates/views.py +++ /dev/null @@ -1,42 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2013 B1 Systems GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import logging - -from django.utils.translation import ugettext_lazy as _ - -from horizon import exceptions -from horizon import tables -from openstack_dashboard import api -from openstack_dashboard.dashboards.admin.aggregates.tables import \ - AdminAggregatesTable - -LOG = logging.getLogger(__name__) - - -class AdminIndexView(tables.DataTableView): - table_class = AdminAggregatesTable - template_name = 'admin/aggregates/index.html' - - def get_data(self): - aggregates = [] - try: - aggregates = api.nova.aggregate_list(self.request) - except Exception: - exceptions.handle(self.request, - _('Unable to retrieve aggregate list.')) - - return aggregates diff --git a/openstack_dashboard/dashboards/admin/dashboard.py b/openstack_dashboard/dashboards/admin/dashboard.py index 9291aea5a3..ce4456c7ec 100644 --- a/openstack_dashboard/dashboards/admin/dashboard.py +++ b/openstack_dashboard/dashboards/admin/dashboard.py @@ -22,7 +22,7 @@ import horizon class SystemPanels(horizon.PanelGroup): slug = "admin" name = _("System Panel") - panels = ('overview', 'aggregates', 'hypervisors', 'instances', 'volumes', + panels = ('overview', 'hypervisors', 'instances', 'volumes', 'flavors', 'images', 'networks', 'routers', 'info') diff --git a/openstack_dashboard/dashboards/admin/info/tables.py b/openstack_dashboard/dashboards/admin/info/tables.py index 56a3cabb5f..7ab85a2cc4 100644 --- a/openstack_dashboard/dashboards/admin/info/tables.py +++ b/openstack_dashboard/dashboards/admin/info/tables.py @@ -1,7 +1,7 @@ import logging from django import template -from django.template.defaultfilters import timesince +from django.template import defaultfilters as filters from django.utils.translation import ugettext_lazy as _ from horizon import tables @@ -89,6 +89,41 @@ class ServicesTable(tables.DataTable): status_columns = ["enabled"] +def get_available(zone): + return zone.zoneState['available'] + + +def get_hosts(zone): + hosts = zone.hosts + host_details = [] + for name, services in hosts.items(): + up = all([s['active'] and s['available'] for k, s in services.items()]) + up = _("Services Up") if up else _("Services Down") + host_details.append("%(host)s (%(up)s)" % {'host': name, 'up': up}) + return host_details + + +class ZonesTable(tables.DataTable): + name = tables.Column('zoneName', verbose_name=_('Name')) + hosts = tables.Column(get_hosts, + verbose_name=_('Hosts'), + wrap_list=True, + filters=(filters.unordered_list,)) + available = tables.Column(get_available, + verbose_name=_('Available'), + status=True, + filters=(filters.yesno, filters.capfirst)) + + def get_object_id(self, zone): + return zone.zoneName + + class Meta: + name = "zones" + verbose_name = _("Availability Zones") + multi_select = False + status_columns = ["available"] + + class NovaServiceFilterAction(tables.FilterAction): def filter(self, table, services, filter_string): q = filter_string.lower() @@ -109,7 +144,7 @@ class NovaServicesTable(tables.DataTable): state = tables.Column('state', verbose_name=_('State')) updated_at = tables.Column('updated_at', verbose_name=_('Updated At'), - filters=(parse_isotime, timesince)) + filters=(parse_isotime, filters.timesince)) def get_object_id(self, obj): return "%s-%s-%s" % (obj.binary, obj.host, obj.zone) @@ -119,3 +154,31 @@ class NovaServicesTable(tables.DataTable): verbose_name = _("Compute Services") table_actions = (NovaServiceFilterAction,) multi_select = False + + +def get_hosts(aggregate): + return [host for host in aggregate.hosts] + + +def get_metadata(aggregate): + return [' = '.join([key, val]) for key, val + in aggregate.metadata.iteritems()] + + +class AggregatesTable(tables.DataTable): + name = tables.Column("name", + verbose_name=_("Name")) + availability_zone = tables.Column("availability_zone", + verbose_name=_("Availability Zone")) + hosts = tables.Column(get_hosts, + verbose_name=_("Hosts"), + wrap_list=True, + filters=(filters.unordered_list,)) + metadata = tables.Column(get_metadata, + verbose_name=_("Metadata"), + wrap_list=True, + filters=(filters.unordered_list,)) + + class Meta: + name = "aggregates" + verbose_name = _("Host Aggregates") diff --git a/openstack_dashboard/dashboards/admin/info/tabs.py b/openstack_dashboard/dashboards/admin/info/tabs.py index b2ce71515e..39b93d76b4 100644 --- a/openstack_dashboard/dashboards/admin/info/tabs.py +++ b/openstack_dashboard/dashboards/admin/info/tabs.py @@ -24,9 +24,11 @@ from openstack_dashboard.api import keystone from openstack_dashboard.api import nova from openstack_dashboard.usage import quotas +from openstack_dashboard.dashboards.admin.info.tables import AggregatesTable from openstack_dashboard.dashboards.admin.info.tables import NovaServicesTable from openstack_dashboard.dashboards.admin.info.tables import QuotasTable from openstack_dashboard.dashboards.admin.info.tables import ServicesTable +from openstack_dashboard.dashboards.admin.info.tables import ZonesTable class DefaultQuotasTab(tabs.TableTab): @@ -68,6 +70,39 @@ class ServicesTab(tabs.TableTab): return services +class ZonesTab(tabs.TableTab): + table_classes = (ZonesTable,) + name = _("Availability Zones") + slug = "zones" + template_name = ("horizon/common/_detail_table.html") + + def get_zones_data(self): + request = self.tab_group.request + zones = [] + try: + zones = nova.availability_zone_list(request, detailed=True) + except Exception: + msg = _('Unable to retrieve availability zone data.') + exceptions.handle(request, msg) + return zones + + +class HostAggregatesTab(tabs.TableTab): + table_classes = (AggregatesTable,) + name = _("Host Aggregates") + slug = "aggregates" + template_name = ("horizon/common/_detail_table.html") + + def get_aggregates_data(self): + aggregates = [] + try: + aggregates = nova.aggregate_list(self.tab_group.request) + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve host aggregates list.')) + return aggregates + + class NovaServicesTab(tabs.TableTab): table_classes = (NovaServicesTable,) name = _("Compute Services") @@ -88,5 +123,6 @@ class NovaServicesTab(tabs.TableTab): class SystemInfoTabs(tabs.TabGroup): slug = "system_info" - tabs = (ServicesTab, NovaServicesTab, DefaultQuotasTab,) + tabs = (ServicesTab, NovaServicesTab, ZonesTab, HostAggregatesTab, + DefaultQuotasTab) sticky = True diff --git a/openstack_dashboard/dashboards/admin/info/tests.py b/openstack_dashboard/dashboards/admin/info/tests.py index a42ed5465a..39deea1712 100644 --- a/openstack_dashboard/dashboards/admin/info/tests.py +++ b/openstack_dashboard/dashboards/admin/info/tests.py @@ -24,17 +24,24 @@ from openstack_dashboard.test import helpers as test INDEX_URL = reverse('horizon:admin:info:index') -class ServicesViewTests(test.BaseAdminViewTests): +class SystemInfoViewTests(test.BaseAdminViewTests): - @test.create_stubs({api.nova: ('default_quota_get', 'service_list',), + @test.create_stubs({api.nova: ('default_quota_get', + 'service_list', + 'availability_zone_list', + 'aggregate_list'), api.cinder: ('default_quota_get',)}) def test_index(self): api.nova.default_quota_get(IsA(http.HttpRequest), self.tenant.id).AndReturn(self.quotas.nova) api.cinder.default_quota_get(IsA(http.HttpRequest), self.tenant.id) \ - .AndReturn(self.cinder_quotas.first()) + .AndReturn(self.cinder_quotas.first()) services = self.services.list() api.nova.service_list(IsA(http.HttpRequest)).AndReturn(services) + api.nova.availability_zone_list(IsA(http.HttpRequest), detailed=True) \ + .AndReturn(self.availability_zones.list()) + api.nova.aggregate_list(IsA(http.HttpRequest)) \ + .AndReturn(self.aggregates.list()) self.mox.ReplayAll() @@ -68,8 +75,19 @@ class ServicesViewTests(test.BaseAdminViewTests): ''], ordered=False) + zones_tab = res.context['tab_group'].get_tab('zones') + self.assertQuerysetEqual(zones_tab._tables['zones'].data, + ['']) + + aggregates_tab = res.context['tab_group'].get_tab('aggregates') + self.assertQuerysetEqual(aggregates_tab._tables['aggregates'].data, + ['', '']) + @test.create_stubs({api.base: ('is_service_enabled',), - api.nova: ('default_quota_get', 'service_list',), + api.nova: ('default_quota_get', + 'service_list', + 'availability_zone_list', + 'aggregate_list'), api.cinder: ('default_quota_get',)}) def test_index_with_neutron_disabled(self): # Neutron does not have an API for getting default system @@ -80,10 +98,15 @@ class ServicesViewTests(test.BaseAdminViewTests): 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()) + .AndReturn(self.cinder_quotas.first()) services = self.services.list() api.nova.service_list(IsA(http.HttpRequest)).AndReturn(services) + api.nova.availability_zone_list(IsA(http.HttpRequest), detailed=True) \ + .AndReturn(self.availability_zones.list()) + api.nova.aggregate_list(IsA(http.HttpRequest)) \ + .AndReturn(self.aggregates.list()) self.mox.ReplayAll() @@ -118,3 +141,11 @@ class ServicesViewTests(test.BaseAdminViewTests): '', ''], ordered=False) + + zones_tab = res.context['tab_group'].get_tab('zones') + self.assertQuerysetEqual(zones_tab._tables['zones'].data, + ['']) + + aggregates_tab = res.context['tab_group'].get_tab('aggregates') + self.assertQuerysetEqual(aggregates_tab._tables['aggregates'].data, + ['', '']) diff --git a/openstack_dashboard/test/test_data/nova_data.py b/openstack_dashboard/test/test_data/nova_data.py index 34f7a08715..1c5f0b10a3 100644 --- a/openstack_dashboard/test/test_data/nova_data.py +++ b/openstack_dashboard/test/test_data/nova_data.py @@ -490,7 +490,18 @@ def data(TEST): TEST.availability_zones.add( availability_zones.AvailabilityZone( availability_zones.AvailabilityZoneManager(None), - {'zoneName': 'nova', 'zoneState': {'available': True}} + { + 'zoneName': 'nova', + 'zoneState': {'available': True}, + 'hosts': { + "host001": { + "nova-network": { + "active": True, + "available": True + } + } + } + } ) )