diff --git a/openstack_dashboard/api/nova.py b/openstack_dashboard/api/nova.py index ab7baeba1..6815439f6 100644 --- a/openstack_dashboard/api/nova.py +++ b/openstack_dashboard/api/nova.py @@ -616,3 +616,7 @@ def tenant_absolute_limits(request, reserved=False): def availability_zone_list(request, detailed=False): return novaclient(request).availability_zones.list(detailed=detailed) + + +def service_list(request): + return novaclient(request).services.list() diff --git a/openstack_dashboard/dashboards/admin/info/tables.py b/openstack_dashboard/dashboards/admin/info/tables.py index b5de08ebc..56a3cabb5 100644 --- a/openstack_dashboard/dashboards/admin/info/tables.py +++ b/openstack_dashboard/dashboards/admin/info/tables.py @@ -1,9 +1,11 @@ import logging from django import template +from django.template.defaultfilters import timesince from django.utils.translation import ugettext_lazy as _ from horizon import tables +from horizon.utils.filters import parse_isotime LOG = logging.getLogger(__name__) @@ -85,3 +87,35 @@ class ServicesTable(tables.DataTable): table_actions = (ServiceFilterAction,) multi_select = False status_columns = ["enabled"] + + +class NovaServiceFilterAction(tables.FilterAction): + def filter(self, table, services, filter_string): + q = filter_string.lower() + + def comp(service): + if q in service.type.lower(): + return True + return False + + return filter(comp, services) + + +class NovaServicesTable(tables.DataTable): + binary = tables.Column("binary", verbose_name=_('Name')) + host = tables.Column('host', verbose_name=_('Host')) + zone = tables.Column('zone', verbose_name=_('Zone')) + status = tables.Column('status', verbose_name=_('Status')) + state = tables.Column('state', verbose_name=_('State')) + updated_at = tables.Column('updated_at', + verbose_name=_('Updated At'), + filters=(parse_isotime, timesince)) + + def get_object_id(self, obj): + return "%s-%s-%s" % (obj.binary, obj.host, obj.zone) + + class Meta: + name = "nova_services" + verbose_name = _("Compute Services") + table_actions = (NovaServiceFilterAction,) + multi_select = False diff --git a/openstack_dashboard/dashboards/admin/info/tabs.py b/openstack_dashboard/dashboards/admin/info/tabs.py index 208a8441a..0c9649be3 100644 --- a/openstack_dashboard/dashboards/admin/info/tabs.py +++ b/openstack_dashboard/dashboards/admin/info/tabs.py @@ -20,8 +20,10 @@ from horizon import exceptions from horizon import tabs 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 NovaServicesTable from openstack_dashboard.dashboards.admin.info.tables import QuotasTable from openstack_dashboard.dashboards.admin.info.tables import ServicesTable @@ -59,7 +61,25 @@ class ServicesTab(tabs.TableTab): return services +class NovaServicesTab(tabs.TableTab): + table_classes = (NovaServicesTable,) + name = _("Compute Services") + slug = "nova_services" + template_name = ("horizon/common/_detail_table.html") + + def get_nova_services_data(self): + try: + services = nova.service_list(self.tab_group.request) + except Exception: + services = [] + msg = _('Unable to get nova services list.') + exceptions.check_message(["Connection", "refused"], msg) + raise + + return services + + class SystemInfoTabs(tabs.TabGroup): slug = "system_info" - tabs = (ServicesTab, DefaultQuotasTab,) + tabs = (ServicesTab, NovaServicesTab, DefaultQuotasTab,) sticky = True diff --git a/openstack_dashboard/dashboards/admin/info/tests.py b/openstack_dashboard/dashboards/admin/info/tests.py index e794d7d4a..79cd383e0 100644 --- a/openstack_dashboard/dashboards/admin/info/tests.py +++ b/openstack_dashboard/dashboards/admin/info/tests.py @@ -25,6 +25,7 @@ INDEX_URL = reverse('horizon:admin:info:index') class ServicesViewTests(test.BaseAdminViewTests): + @test.create_stubs({api.nova: ('service_list',)}) def test_index(self): self.mox.StubOutWithMock(api.nova, 'default_quota_get') self.mox.StubOutWithMock(api.cinder, 'default_quota_get') @@ -32,6 +33,8 @@ class ServicesViewTests(test.BaseAdminViewTests): self.tenant.id).AndReturn(self.quotas.nova) api.cinder.default_quota_get(IsA(http.HttpRequest), self.tenant.id) \ .AndReturn(self.cinder_quotas.first()) + services = self.services.list() + api.nova.service_list(IsA(http.HttpRequest)).AndReturn(services) self.mox.ReplayAll() diff --git a/openstack_dashboard/test/test_data/nova_data.py b/openstack_dashboard/test/test_data/nova_data.py index aba2b5976..526482308 100644 --- a/openstack_dashboard/test/test_data/nova_data.py +++ b/openstack_dashboard/test/test_data/nova_data.py @@ -25,6 +25,7 @@ from novaclient.v1_1 import quotas from novaclient.v1_1 import security_group_rules as rules from novaclient.v1_1 import security_groups as sec_groups from novaclient.v1_1 import servers +from novaclient.v1_1 import services from novaclient.v1_1 import usage from novaclient.v1_1 import volume_snapshots as vol_snaps from novaclient.v1_1 import volume_types @@ -164,6 +165,7 @@ def data(TEST): TEST.volume_types = TestDataContainer() TEST.availability_zones = TestDataContainer() TEST.hypervisors = TestDataContainer() + TEST.services = TestDataContainer() # Data return by novaclient. # It is used if API layer does data conversion. @@ -512,3 +514,30 @@ def data(TEST): } ) TEST.hypervisors.add(hypervisor_1) + + # Services + service_1 = services.Service(services.ServiceManager(None), + { + "status": "enabled", + "binary": "nova-conductor", + "zone": "internal", + "state": "up", + "updated_at": "2013-07-08T05:21:00.000000", + "host": "devstack001", + "disabled_reason": None + } + ) + + service_2 = services.Service(services.ServiceManager(None), + { + "status": "enabled", + "binary": "nova-compute", + "zone": "nova", + "state": "up", + "updated_at": "2013-07-08T05:20:51.000000", + "host": "devstack001", + "disabled_reason": None + } + ) + TEST.services.add(service_1) + TEST.services.add(service_2)