diff --git a/tuskar_ui/infrastructure/dashboard.py b/tuskar_ui/infrastructure/dashboard.py index 086a1b246..d1a9368c7 100644 --- a/tuskar_ui/infrastructure/dashboard.py +++ b/tuskar_ui/infrastructure/dashboard.py @@ -23,6 +23,7 @@ class BasePanels(horizon.PanelGroup): 'overcloud', 'plans', 'parameters', + 'roles', 'nodes', 'flavors', 'images', diff --git a/tuskar_ui/infrastructure/nodes/tables.py b/tuskar_ui/infrastructure/nodes/tables.py index c7f122b7f..446d3ea7a 100644 --- a/tuskar_ui/infrastructure/nodes/tables.py +++ b/tuskar_ui/infrastructure/nodes/tables.py @@ -54,9 +54,8 @@ def get_role_link(datum): # TODO(tzumainn): this could probably be done more efficiently # by getting the resource for all nodes at once if datum.role_id: - return reverse('horizon:infrastructure:overcloud:role', - kwargs={'stack_id': datum.stack_id, - 'role_id': datum.role_id}) + return reverse('horizon:infrastructure:roles:detail', + kwargs={'role_id': datum.role_id}) class RegisteredNodesTable(tables.DataTable): diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/details.html b/tuskar_ui/infrastructure/nodes/templates/nodes/details.html index deee69f28..0383891f3 100644 --- a/tuskar_ui/infrastructure/nodes/templates/nodes/details.html +++ b/tuskar_ui/infrastructure/nodes/templates/nodes/details.html @@ -19,7 +19,7 @@
{% trans "Deployment Role" %}
{% if stack and role %} -
{{ role.name }}
+
{{ role.name }}
{% else %}
{% endif %} diff --git a/tuskar_ui/infrastructure/overcloud/tables.py b/tuskar_ui/infrastructure/overcloud/tables.py index 573a1d697..2be5d6f91 100644 --- a/tuskar_ui/infrastructure/overcloud/tables.py +++ b/tuskar_ui/infrastructure/overcloud/tables.py @@ -16,17 +16,6 @@ from django.utils.translation import ugettext_lazy as _ from horizon import tables -from tuskar_ui.infrastructure.nodes import tables as nodes_tables - - -class OvercloudRoleNodeTable(nodes_tables.RegisteredNodesTable): - - class Meta: - name = "overcloud_role__nodetable" - verbose_name = _("Nodes") - table_actions = () - row_actions = () - class ConfigurationTable(tables.DataTable): diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_detail_overview.html b/tuskar_ui/infrastructure/overcloud/templates/overcloud/_detail_overview.html index 4aa448d22..89d81ea2b 100644 --- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_detail_overview.html +++ b/tuskar_ui/infrastructure/overcloud/templates/overcloud/_detail_overview.html @@ -191,7 +191,7 @@ nova flavor-create m1.tiny 1 512 2 1 {% for role in roles %} {{ role.name }} ({{ role.total_node_count }}) diff --git a/tuskar_ui/infrastructure/overcloud/urls.py b/tuskar_ui/infrastructure/overcloud/urls.py index 6eb5ec9a4..d2d88c782 100644 --- a/tuskar_ui/infrastructure/overcloud/urls.py +++ b/tuskar_ui/infrastructure/overcloud/urls.py @@ -25,8 +25,6 @@ urlpatterns = urls.patterns( name='undeploy_in_progress'), urls.url(r'^(?P[^/]+)/$', views.DetailView.as_view(), name='detail'), - urls.url(r'^(?P[^/]+)/role/(?P[^/]+)$', - views.OvercloudRoleView.as_view(), name='role'), urls.url(r'^(?P[^/]+)/undeploy-confirmation$', views.UndeployConfirmationView.as_view(), name='undeploy_confirmation'), diff --git a/tuskar_ui/infrastructure/overcloud/views.py b/tuskar_ui/infrastructure/overcloud/views.py index fc5ce103c..1d3048374 100644 --- a/tuskar_ui/infrastructure/overcloud/views.py +++ b/tuskar_ui/infrastructure/overcloud/views.py @@ -18,13 +18,11 @@ from django.views.generic import base as base_views from horizon import exceptions as horizon_exceptions import horizon.forms from horizon import messages -from horizon import tables as horizon_tables from horizon import tabs as horizon_tabs from horizon.utils import memoized from tuskar_ui import api from tuskar_ui.infrastructure.overcloud import forms -from tuskar_ui.infrastructure.overcloud import tables from tuskar_ui.infrastructure.overcloud import tabs @@ -46,15 +44,6 @@ class StackMixin(object): return stack -class OvercloudRoleMixin(object): - @memoized.memoized - def get_role(self, redirect=None): - role_id = self.kwargs['role_id'] - role = api.tuskar.OvercloudRole.get(self.request, role_id, - _error_redirect=redirect) - return role - - class IndexView(base_views.RedirectView): permanent = False @@ -153,49 +142,3 @@ class UndeployInProgressView(horizon_tabs.TabView, StackMixin, ): self).get_context_data(**kwargs) context['stack'] = self.get_stack_or_redirect() return context - - -class OvercloudRoleView(horizon_tables.DataTableView, - OvercloudRoleMixin, StackMixin): - table_class = tables.OvercloudRoleNodeTable - template_name = 'infrastructure/overcloud/overcloud_role.html' - - @memoized.memoized - def _get_nodes(self, stack, role): - resources = stack.resources_by_role(role, with_joins=True) - nodes = [r.node for r in resources] - - for node in nodes: - # TODO(tzumainn): this could probably be done more efficiently - # by getting the resource for all nodes at once - try: - resource = api.heat.Resource.get_by_node(self.request, node) - node.role_name = resource.role.name - node.role_id = resource.role.id - node.stack_id = resource.stack.id - except horizon_exceptions.NotFound: - node.role_name = '-' - - return nodes - - def get_data(self): - stack = self.get_stack() - redirect = reverse(DETAIL_URL, - args=(stack.id,)) - role = self.get_role(redirect) - return self._get_nodes(stack, role) - - def get_context_data(self, **kwargs): - context = super(OvercloudRoleView, self).get_context_data(**kwargs) - - stack = self.get_stack() - redirect = reverse(DETAIL_URL, - args=(stack.id,)) - role = self.get_role(redirect) - context['role'] = role - # TODO(tzumainn) we need to do this from plan parameters - context['image_name'] = 'FIXME' - context['nodes'] = self._get_nodes(stack, role) - context['flavor'] = None - - return context diff --git a/tuskar_ui/infrastructure/roles/__init__.py b/tuskar_ui/infrastructure/roles/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tuskar_ui/infrastructure/roles/panel.py b/tuskar_ui/infrastructure/roles/panel.py new file mode 100644 index 000000000..9fc98f096 --- /dev/null +++ b/tuskar_ui/infrastructure/roles/panel.py @@ -0,0 +1,27 @@ +# -*- coding: utf8 -*- +# +# 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 tuskar_ui.infrastructure import dashboard + + +class Roles(horizon.Panel): + name = _("Deployment Roles") + slug = "roles" + + +dashboard.Infrastructure.register(Roles) diff --git a/tuskar_ui/infrastructure/roles/tables.py b/tuskar_ui/infrastructure/roles/tables.py new file mode 100644 index 000000000..836f96bcc --- /dev/null +++ b/tuskar_ui/infrastructure/roles/tables.py @@ -0,0 +1,41 @@ +# -*- coding: utf8 -*- +# +# 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 tuskar_ui.infrastructure.nodes import tables as nodes_tables + + +class RolesTable(tables.DataTable): + + name = tables.Column('name', + link="horizon:infrastructure:roles:detail", + verbose_name=_("Image Name")) + + class Meta: + name = "roles" + verbose_name = _("Deployment Roles") + table_actions = () + row_actions = () + + +class NodeTable(nodes_tables.RegisteredNodesTable): + + class Meta: + name = "nodetable" + verbose_name = _("Nodes") + table_actions = () + row_actions = () diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/overcloud_role.html b/tuskar_ui/infrastructure/roles/templates/roles/detail.html similarity index 100% rename from tuskar_ui/infrastructure/overcloud/templates/overcloud/overcloud_role.html rename to tuskar_ui/infrastructure/roles/templates/roles/detail.html diff --git a/tuskar_ui/infrastructure/roles/templates/roles/index.html b/tuskar_ui/infrastructure/roles/templates/roles/index.html new file mode 100644 index 000000000..b9991e35e --- /dev/null +++ b/tuskar_ui/infrastructure/roles/templates/roles/index.html @@ -0,0 +1,17 @@ +{% extends 'infrastructure/base.html' %} +{% load i18n %} +{% block title %}{% trans 'Deployment Roles' %}{% endblock %} + +{% block page_header %} + {% include 'horizon/common/_page_header.html' with title=_('Deployment Roles') %} +{% endblock page_header %} + +{% block main %} +
+
+

{% trans "Deployment Roles" %}

+ {{ table.render }} +
+
+ +{% endblock %} diff --git a/tuskar_ui/infrastructure/roles/tests.py b/tuskar_ui/infrastructure/roles/tests.py new file mode 100644 index 000000000..69a4c0f5d --- /dev/null +++ b/tuskar_ui/infrastructure/roles/tests.py @@ -0,0 +1,69 @@ +# -*- coding: utf8 -*- +# +# 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 contextlib + +from django.core import urlresolvers + +from mock import patch, call # noqa + +from openstack_dashboard.test.test_data import utils + +from tuskar_ui import api +from tuskar_ui.test import helpers as test +from tuskar_ui.test.test_data import heat_data +from tuskar_ui.test.test_data import node_data +from tuskar_ui.test.test_data import tuskar_data + + +INDEX_URL = urlresolvers.reverse( + 'horizon:infrastructure:roles:index') +DETAIL_URL = urlresolvers.reverse( + 'horizon:infrastructure:roles:detail', args=('role-1',)) + +TEST_DATA = utils.TestDataContainer() +node_data.data(TEST_DATA) +heat_data.data(TEST_DATA) +tuskar_data.data(TEST_DATA) + + +class RolesTest(test.BaseAdminViewTests): + + def test_index_get(self): + roles = [api.tuskar.OvercloudRole(role) + for role in TEST_DATA.tuskarclient_roles.list()] + + with patch('tuskar_ui.api.tuskar.OvercloudRole.list', + return_value=roles): + res = self.client.get(INDEX_URL) + + self.assertTemplateUsed(res, 'infrastructure/roles/index.html') + + def test_detail_get(self): + roles = [api.tuskar.OvercloudRole(role) + for role in TEST_DATA.tuskarclient_roles.list()] + + with contextlib.nested( + patch('tuskar_ui.api.tuskar.OvercloudRole', **{ + 'spec_set': ['list', 'get'], + 'list.return_value': roles, + 'get.return_value': roles[0], + }), + patch('tuskar_ui.api.heat.Stack.events', + return_value=[]), + ): + res = self.client.get(DETAIL_URL) + + self.assertTemplateUsed( + res, 'infrastructure/roles/detail.html') diff --git a/tuskar_ui/infrastructure/roles/urls.py b/tuskar_ui/infrastructure/roles/urls.py new file mode 100644 index 000000000..2132c72ca --- /dev/null +++ b/tuskar_ui/infrastructure/roles/urls.py @@ -0,0 +1,25 @@ +# -*- coding: utf8 -*- +# +# 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 import urls + +from tuskar_ui.infrastructure.roles import views + + +urlpatterns = urls.patterns( + '', + urls.url(r'^$', views.IndexView.as_view(), name='index'), + urls.url(r'^(?P[^/]+)/$', views.DetailView.as_view(), + name='detail'), +) diff --git a/tuskar_ui/infrastructure/roles/views.py b/tuskar_ui/infrastructure/roles/views.py new file mode 100644 index 000000000..06eb2d7a3 --- /dev/null +++ b/tuskar_ui/infrastructure/roles/views.py @@ -0,0 +1,102 @@ +# -*- coding: utf8 -*- +# +# 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 horizon import exceptions as horizon_exceptions +from horizon import tables as horizon_tables +from horizon.utils import memoized + +from tuskar_ui import api +from tuskar_ui.infrastructure.roles import tables + + +INDEX_URL = 'horizon:infrastructure:roles:index' + + +class OvercloudRoleMixin(object): + @memoized.memoized + def get_role(self, redirect=None): + role_id = self.kwargs['role_id'] + role = api.tuskar.OvercloudRole.get(self.request, role_id, + _error_redirect=redirect) + return role + + +class StackMixin(object): + @memoized.memoized + def get_stack(self, redirect=None): + if redirect is None: + redirect = reverse(INDEX_URL) + plan = api.tuskar.OvercloudPlan.get_the_plan(self.request) + stack = api.heat.Stack.get_by_plan(self.request, plan) + + return stack + + +class IndexView(horizon_tables.DataTableView): + table_class = tables.RolesTable + template_name = "infrastructure/roles/index.html" + + def get_data(self): + return api.tuskar.OvercloudRole.list(self.request) + + +class DetailView(horizon_tables.DataTableView, OvercloudRoleMixin, StackMixin): + table_class = tables.NodeTable + template_name = 'infrastructure/roles/detail.html' + + @memoized.memoized + def _get_nodes(self, stack, role): + resources = stack.resources_by_role(role, with_joins=True) + nodes = [r.node for r in resources] + + for node in nodes: + # TODO(tzumainn): this could probably be done more efficiently + # by getting the resource for all nodes at once + try: + resource = api.heat.Resource.get_by_node(self.request, node) + node.role_name = resource.role.name + node.role_id = resource.role.id + node.stack_id = resource.stack.id + except horizon_exceptions.NotFound: + node.role_name = '-' + + return nodes + + def get_data(self): + redirect = reverse(INDEX_URL) + stack = self.get_stack(redirect) + if stack: + role = self.get_role(redirect) + return self._get_nodes(stack, role) + return [] + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + redirect = reverse(INDEX_URL) + + stack = self.get_stack(redirect) + role = self.get_role(redirect) + + context['role'] = role + # TODO(tzumainn) we need to do this from plan parameters + context['image_name'] = 'FIXME' + if stack: + context['nodes'] = self._get_nodes(stack, role) + else: + context['nodes'] = [] + context['flavor'] = None + + return context