Add Role Panel
Moved OvercloudRoleView from overcloud into DetailView in roles Change-Id: I53f6d4873df728487d6567e0092c66761c84d864
This commit is contained in:
parent
ab767a61b0
commit
907210928c
@ -23,6 +23,7 @@ class BasePanels(horizon.PanelGroup):
|
|||||||
'overcloud',
|
'overcloud',
|
||||||
'plans',
|
'plans',
|
||||||
'parameters',
|
'parameters',
|
||||||
|
'roles',
|
||||||
'nodes',
|
'nodes',
|
||||||
'flavors',
|
'flavors',
|
||||||
'images',
|
'images',
|
||||||
|
@ -54,9 +54,8 @@ def get_role_link(datum):
|
|||||||
# TODO(tzumainn): this could probably be done more efficiently
|
# TODO(tzumainn): this could probably be done more efficiently
|
||||||
# by getting the resource for all nodes at once
|
# by getting the resource for all nodes at once
|
||||||
if datum.role_id:
|
if datum.role_id:
|
||||||
return reverse('horizon:infrastructure:overcloud:role',
|
return reverse('horizon:infrastructure:roles:detail',
|
||||||
kwargs={'stack_id': datum.stack_id,
|
kwargs={'role_id': datum.role_id})
|
||||||
'role_id': datum.role_id})
|
|
||||||
|
|
||||||
|
|
||||||
class RegisteredNodesTable(tables.DataTable):
|
class RegisteredNodesTable(tables.DataTable):
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<dl class="clearfix">
|
<dl class="clearfix">
|
||||||
<dt>{% trans "Deployment Role" %}</dt>
|
<dt>{% trans "Deployment Role" %}</dt>
|
||||||
{% if stack and role %}
|
{% if stack and role %}
|
||||||
<dd><a href="{% url 'horizon:infrastructure:overcloud:role' stack.id role.id %}">{{ role.name }}</a></dd>
|
<dd><a href="{% url 'horizon:infrastructure:roles:detail' role.id %}">{{ role.name }}</a></dd>
|
||||||
{% else %}
|
{% else %}
|
||||||
<dd>—</dd>
|
<dd>—</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -16,17 +16,6 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
from horizon import tables
|
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):
|
class ConfigurationTable(tables.DataTable):
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ nova flavor-create m1.tiny 1 512 2 1
|
|||||||
{% for role in roles %}
|
{% for role in roles %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a
|
<td><a
|
||||||
href="{% url 'horizon:infrastructure:overcloud:role' stack.id role.role.id %}"
|
href="{% url 'horizon:infrastructure:roles:detail' role.role.id %}"
|
||||||
>{{ role.name }} <span class="badge">({{ role.total_node_count }})</span></a>
|
>{{ role.name }} <span class="badge">({{ role.total_node_count }})</span></a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -25,8 +25,6 @@ urlpatterns = urls.patterns(
|
|||||||
name='undeploy_in_progress'),
|
name='undeploy_in_progress'),
|
||||||
urls.url(r'^(?P<stack_id>[^/]+)/$', views.DetailView.as_view(),
|
urls.url(r'^(?P<stack_id>[^/]+)/$', views.DetailView.as_view(),
|
||||||
name='detail'),
|
name='detail'),
|
||||||
urls.url(r'^(?P<stack_id>[^/]+)/role/(?P<role_id>[^/]+)$',
|
|
||||||
views.OvercloudRoleView.as_view(), name='role'),
|
|
||||||
urls.url(r'^(?P<stack_id>[^/]+)/undeploy-confirmation$',
|
urls.url(r'^(?P<stack_id>[^/]+)/undeploy-confirmation$',
|
||||||
views.UndeployConfirmationView.as_view(),
|
views.UndeployConfirmationView.as_view(),
|
||||||
name='undeploy_confirmation'),
|
name='undeploy_confirmation'),
|
||||||
|
@ -18,13 +18,11 @@ from django.views.generic import base as base_views
|
|||||||
from horizon import exceptions as horizon_exceptions
|
from horizon import exceptions as horizon_exceptions
|
||||||
import horizon.forms
|
import horizon.forms
|
||||||
from horizon import messages
|
from horizon import messages
|
||||||
from horizon import tables as horizon_tables
|
|
||||||
from horizon import tabs as horizon_tabs
|
from horizon import tabs as horizon_tabs
|
||||||
from horizon.utils import memoized
|
from horizon.utils import memoized
|
||||||
|
|
||||||
from tuskar_ui import api
|
from tuskar_ui import api
|
||||||
from tuskar_ui.infrastructure.overcloud import forms
|
from tuskar_ui.infrastructure.overcloud import forms
|
||||||
from tuskar_ui.infrastructure.overcloud import tables
|
|
||||||
from tuskar_ui.infrastructure.overcloud import tabs
|
from tuskar_ui.infrastructure.overcloud import tabs
|
||||||
|
|
||||||
|
|
||||||
@ -46,15 +44,6 @@ class StackMixin(object):
|
|||||||
return stack
|
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):
|
class IndexView(base_views.RedirectView):
|
||||||
permanent = False
|
permanent = False
|
||||||
|
|
||||||
@ -153,49 +142,3 @@ class UndeployInProgressView(horizon_tabs.TabView, StackMixin, ):
|
|||||||
self).get_context_data(**kwargs)
|
self).get_context_data(**kwargs)
|
||||||
context['stack'] = self.get_stack_or_redirect()
|
context['stack'] = self.get_stack_or_redirect()
|
||||||
return context
|
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
|
|
||||||
|
0
tuskar_ui/infrastructure/roles/__init__.py
Normal file
0
tuskar_ui/infrastructure/roles/__init__.py
Normal file
27
tuskar_ui/infrastructure/roles/panel.py
Normal file
27
tuskar_ui/infrastructure/roles/panel.py
Normal file
@ -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)
|
41
tuskar_ui/infrastructure/roles/tables.py
Normal file
41
tuskar_ui/infrastructure/roles/tables.py
Normal file
@ -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 = ()
|
17
tuskar_ui/infrastructure/roles/templates/roles/index.html
Normal file
17
tuskar_ui/infrastructure/roles/templates/roles/index.html
Normal file
@ -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 %}
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="span12">
|
||||||
|
<h4>{% trans "Deployment Roles" %}</h4>
|
||||||
|
{{ table.render }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
69
tuskar_ui/infrastructure/roles/tests.py
Normal file
69
tuskar_ui/infrastructure/roles/tests.py
Normal file
@ -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')
|
25
tuskar_ui/infrastructure/roles/urls.py
Normal file
25
tuskar_ui/infrastructure/roles/urls.py
Normal file
@ -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<role_id>[^/]+)/$', views.DetailView.as_view(),
|
||||||
|
name='detail'),
|
||||||
|
)
|
102
tuskar_ui/infrastructure/roles/views.py
Normal file
102
tuskar_ui/infrastructure/roles/views.py
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user