diff --git a/blazar_dashboard/api/client.py b/blazar_dashboard/api/client.py index f7a975d..1fffba1 100644 --- a/blazar_dashboard/api/client.py +++ b/blazar_dashboard/api/client.py @@ -39,6 +39,16 @@ class Lease(base.APIDictWrapper): super(Lease, self).__init__(apiresource) +class Host(base.APIDictWrapper): + """Represents one Blazar host.""" + + _attrs = ['id', 'hypervisor_hostname', 'hypervisor_type', 'vcpus', + 'cpu_info', 'memory_mb', 'local_gb'] + + def __init__(self, apiresource): + super(Host, self).__init__(apiresource) + + @memoized def blazarclient(request): try: @@ -82,3 +92,9 @@ def lease_update(request, lease_id, **kwargs): def lease_delete(request, lease_id): """Delete a lease.""" blazarclient(request).lease.delete(lease_id) + + +def host_list(request): + """List hosts.""" + hosts = blazarclient(request).host.list() + return [Host(h) for h in hosts] diff --git a/blazar_dashboard/content/hosts/__init__.py b/blazar_dashboard/content/hosts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blazar_dashboard/content/hosts/panel.py b/blazar_dashboard/content/hosts/panel.py new file mode 100644 index 0000000..7e8a6e8 --- /dev/null +++ b/blazar_dashboard/content/hosts/panel.py @@ -0,0 +1,19 @@ +# 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 + + +class Hosts(horizon.Panel): + name = _("Hosts") + slug = "hosts" diff --git a/blazar_dashboard/content/hosts/tables.py b/blazar_dashboard/content/hosts/tables.py new file mode 100644 index 0000000..b4347bf --- /dev/null +++ b/blazar_dashboard/content/hosts/tables.py @@ -0,0 +1,29 @@ +# 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 +from horizon.templatetags import sizeformat + + +class HostsTable(tables.DataTable): + name = tables.Column("hypervisor_hostname", verbose_name=_("Host name")) + vcpus = tables.Column("vcpus", verbose_name=_("VCPUs")) + memory_mb = tables.Column("memory_mb", verbose_name=_("RAM"), + filters=(sizeformat.mb_float_format,)) + local_gb = tables.Column("local_gb", verbose_name=_("Local Storage"), + filters=(sizeformat.diskgbformat,)) + type = tables.Column("hypervisor_type", verbose_name=_("Hypervisor type")) + + class Meta(object): + name = "hosts" + verbose_name = _("Hosts") diff --git a/blazar_dashboard/content/hosts/templates/hosts/index.html b/blazar_dashboard/content/hosts/templates/hosts/index.html new file mode 100644 index 0000000..f7e0c0d --- /dev/null +++ b/blazar_dashboard/content/hosts/templates/hosts/index.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Hosts" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Hosts") %} +{% endblock page_header %} + +{% block main %} + {{ table.render }} +{% endblock %} diff --git a/blazar_dashboard/content/hosts/tests.py b/blazar_dashboard/content/hosts/tests.py new file mode 100644 index 0000000..d44351b --- /dev/null +++ b/blazar_dashboard/content/hosts/tests.py @@ -0,0 +1,59 @@ +# 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 mox3.mox import IsA + +from blazar_dashboard import api +from blazar_dashboard.test import helpers as test + +import logging +LOG = logging.getLogger(__name__) + +INDEX_TEMPLATE = 'admin/hosts/index.html' +INDEX_URL = reverse('horizon:admin:hosts:index') + + +class HostsTests(test.BaseAdminViewTests): + @test.create_stubs({api.client: ('host_list',)}) + def test_index(self): + hosts = self.hosts.list() + api.client.host_list(IsA(http.HttpRequest)).AndReturn(hosts) + self.mox.ReplayAll() + + res = self.client.get(INDEX_URL) + self.assertTemplateUsed(res, INDEX_TEMPLATE) + self.assertNoMessages(res) + self.assertContains(res, 'compute-1') + self.assertContains(res, 'compute-2') + + @test.create_stubs({api.client: ('host_list',)}) + def test_index_no_hosts(self): + api.client.host_list(IsA(http.HttpRequest)).AndReturn(()) + self.mox.ReplayAll() + + res = self.client.get(INDEX_URL) + self.assertTemplateUsed(res, INDEX_TEMPLATE) + self.assertNoMessages(res) + self.assertContains(res, 'No items to display') + + @test.create_stubs({api.client: ('host_list',)}) + def test_index_error(self): + api.client.host_list( + IsA(http.HttpRequest) + ).AndRaise(self.exceptions.blazar) + self.mox.ReplayAll() + + res = self.client.get(INDEX_URL) + self.assertTemplateUsed(res, INDEX_TEMPLATE) + self.assertMessageCount(res, error=1) diff --git a/blazar_dashboard/content/hosts/urls.py b/blazar_dashboard/content/hosts/urls.py new file mode 100644 index 0000000..69dfc69 --- /dev/null +++ b/blazar_dashboard/content/hosts/urls.py @@ -0,0 +1,20 @@ +# 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 url + +from blazar_dashboard.content.hosts import views + + +urlpatterns = [ + url(r'^$', views.IndexView.as_view(), name='index'), +] diff --git a/blazar_dashboard/content/hosts/views.py b/blazar_dashboard/content/hosts/views.py new file mode 100644 index 0000000..7e57dd2 --- /dev/null +++ b/blazar_dashboard/content/hosts/views.py @@ -0,0 +1,32 @@ +# 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 tables + +from blazar_dashboard import api +from blazar_dashboard.content.hosts import tables as project_tables + + +class IndexView(tables.DataTableView): + table_class = project_tables.HostsTable + template_name = 'admin/hosts/index.html' + + def get_data(self): + try: + hosts = api.client.host_list(self.request) + except Exception: + hosts = [] + msg = _('Unable to retrieve host information.') + exceptions.handle(self.request, msg) + return hosts diff --git a/blazar_dashboard/enabled/_90_admin_reservation_panelgroup.py b/blazar_dashboard/enabled/_90_admin_reservation_panelgroup.py new file mode 100644 index 0000000..b115509 --- /dev/null +++ b/blazar_dashboard/enabled/_90_admin_reservation_panelgroup.py @@ -0,0 +1,20 @@ +# 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 _ + +# The slug of the panel group to be added to HORIZON_CONFIG. Required. +PANEL_GROUP = 'reservation' +# The display name of the PANEL_GROUP. Required. +PANEL_GROUP_NAME = _('Reservation') +# The slug of the dashboard the PANEL_GROUP associated with. Required. +PANEL_GROUP_DASHBOARD = 'admin' diff --git a/blazar_dashboard/enabled/_91_admin_reservation_hosts_panel.py b/blazar_dashboard/enabled/_91_admin_reservation_hosts_panel.py new file mode 100644 index 0000000..b2f8c10 --- /dev/null +++ b/blazar_dashboard/enabled/_91_admin_reservation_hosts_panel.py @@ -0,0 +1,21 @@ +# 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. + +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'hosts' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'reservation' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'admin' + +# Python panel class of the PANEL to be added. +ADD_PANEL = 'blazar_dashboard.content.hosts.panel.Hosts' diff --git a/blazar_dashboard/test/helpers.py b/blazar_dashboard/test/helpers.py index 05e7ab0..0cf69ec 100644 --- a/blazar_dashboard/test/helpers.py +++ b/blazar_dashboard/test/helpers.py @@ -23,3 +23,9 @@ class TestCase(helpers.TestCase): def _setup_test_data(self): super(TestCase, self)._setup_test_data() utils.load_test_data(self) + + +class BaseAdminViewTests(helpers.BaseAdminViewTests): + def _setup_test_data(self): + super(BaseAdminViewTests, self)._setup_test_data() + utils.load_test_data(self) diff --git a/blazar_dashboard/test/test_data/blazar_data.py b/blazar_dashboard/test/test_data/blazar_data.py index 86bd35c..1b795ef 100644 --- a/blazar_dashboard/test/test_data/blazar_data.py +++ b/blazar_dashboard/test/test_data/blazar_data.py @@ -133,9 +133,46 @@ lease_sample2 = { 'trust_id': 'b442a580b9504ababf305bf2b4c49512' } +host_sample1 = { + "status": None, + "hypervisor_type": "QEMU", + "created_at": "2017-10-01 12:00:00", + "updated_at": None, + "hypervisor_hostname": "compute-1", + "memory_mb": 4096, + "cpu_info": "{'dummy': 'true'}", + "vcpus": 1, + "service_name": "blazar", + "hypervisor_version": 2005000, + "local_gb": 128, + "id": "1", + "trust_id": "dummy" +} + +host_sample2 = { + "status": None, + "hypervisor_type": "QEMU", + "created_at": "2017-10-01 12:00:00", + "updated_at": None, + "hypervisor_hostname": "compute-2", + "memory_mb": 4096, + "cpu_info": "{'dummy': 'true'}", + "vcpus": 1, + "service_name": "blazar", + "hypervisor_version": 2005000, + "local_gb": 128, + "id": "2", + "trust_id": "dummy" +} + def data(TEST): TEST.leases = utils.TestDataContainer() TEST.leases.add(api.client.Lease(lease_sample1)) TEST.leases.add(api.client.Lease(lease_sample2)) + + TEST.hosts = utils.TestDataContainer() + + TEST.hosts.add(api.client.Host(host_sample1)) + TEST.hosts.add(api.client.Host(host_sample2)) diff --git a/doc/source/install.rst b/doc/source/install.rst index 89c063c..27bdaec 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -40,7 +40,9 @@ Install Blazar dashboard with all dependencies in your virtual environment:: And enable it in Horizon:: ln -s /path/to/blazar-dashboard/blazar_dashboard/enabled/_90_project_reservations_panelgroup.py openstack_dashboard/local/enabled + ln -s /path/to/blazar-dashboard/blazar_dashboard/enabled/_90_admin_reservation_panelgroup.py openstack_dashboard/local/enabled ln -s /path/to/blazar-dashboard/blazar_dashboard/enabled/_91_project_reservations_leases_panel.py openstack_dashboard/local/enabled + ln -s /path/to/blazar-dashboard/blazar_dashboard/enabled/_91_admin_reservation_hosts_panel.py openstack_dashboard/local/enabled Start horizon and it runs with the newly enabled Blazar dashboard.