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
+ }
+ }
+ }
+ }
)
)