Improve log browsing for deployments.
Added new view for browsing through all deployments for all environments in a tenant. Change-Id: I64bce83be69a22e52994b7f51b96f3bdafbc6867 Partially-implements: blueprint improve-deployment-log-browsing Closes-Bug: #1497261 Depends-On: I80c02a8cfd82260f097474bb512f693aa6734655
This commit is contained in:
parent
a4ad579260
commit
9f72d2fb19
@ -389,6 +389,20 @@ def deployments_list(request, environment_id):
|
||||
return deployments
|
||||
|
||||
|
||||
def deployment_history(request):
|
||||
LOG.debug('Deployment::History')
|
||||
deployment_history = api.muranoclient(request).deployments.list(
|
||||
None, all_environments=True)
|
||||
|
||||
for deployment in deployment_history:
|
||||
reports = deployment_reports(request, deployment.environment_id,
|
||||
deployment.id)
|
||||
deployment.reports = reports
|
||||
|
||||
LOG.debug('Deployment::History {0}'.format(deployment_history))
|
||||
return deployment_history
|
||||
|
||||
|
||||
def deployment_reports(request, environment_id, deployment_id):
|
||||
LOG.debug('Deployment::Reports::List')
|
||||
reports = api.muranoclient(request).deployments.reports(environment_id,
|
||||
|
@ -17,6 +17,7 @@ import json
|
||||
from django.core.urlresolvers import reverse
|
||||
from django import http as django_http
|
||||
from django import shortcuts
|
||||
from django import template
|
||||
from django.template import defaultfilters
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
@ -108,6 +109,19 @@ class CreateEnvironment(tables.LinkAction):
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class DeploymentHistory(tables.LinkAction):
|
||||
name = 'DeploymentHistory'
|
||||
verbose_name = _('Deployment History')
|
||||
url = 'horizon:app-catalog:environments:deployment_history'
|
||||
classes = ('deployment-history')
|
||||
redirect_url = "horizon:app-catalog:environments:index"
|
||||
icon = 'history'
|
||||
policy_rules = (("murano", "list_deployments_all_environments"),)
|
||||
|
||||
def allowed(self, request, datum):
|
||||
return True
|
||||
|
||||
|
||||
class DeleteEnvironment(policy.PolicyTargetMixin, tables.DeleteAction):
|
||||
redirect_url = "horizon:app-catalog:environments:index"
|
||||
policy_rules = (("murano", "delete_environment"),)
|
||||
@ -155,6 +169,10 @@ class AbandonEnvironment(tables.DeleteAction):
|
||||
redirect_url = "horizon:app-catalog:environments:index"
|
||||
policy_rules = (("murano", "delete_environment"),)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AbandonEnvironment, self).__init__(**kwargs)
|
||||
self.icon = 'stop'
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
@ -504,7 +522,8 @@ class EnvironmentsTable(tables.DataTable):
|
||||
status_columns = ['status']
|
||||
no_data_message = _('NO ENVIRONMENTS')
|
||||
table_actions = (CreateEnvironment, DeployEnvironment,
|
||||
DeleteEnvironment, AbandonEnvironment)
|
||||
DeleteEnvironment, AbandonEnvironment,
|
||||
DeploymentHistory)
|
||||
row_actions = (ShowEnvironmentServices, DeployEnvironment,
|
||||
DeleteEnvironment, AbandonEnvironment,
|
||||
UpdateEnvMetadata)
|
||||
@ -692,3 +711,45 @@ class EnvConfigTable(tables.DataTable):
|
||||
class Meta(object):
|
||||
name = 'environment_configuration'
|
||||
verbose_name = _('Deployed Components')
|
||||
|
||||
|
||||
def get_deployment_history_reports(deployment):
|
||||
template_name = 'deployments/_cell_reports.html'
|
||||
context = {
|
||||
"reports": deployment.reports,
|
||||
}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
def get_deployment_history_services(deployment):
|
||||
template_name = 'deployments/_cell_services.html'
|
||||
services = {}
|
||||
for service in deployment.description['services']:
|
||||
service_type = service['?']['type']
|
||||
if service_type.find('/') != -1:
|
||||
service_type = service_type[:service_type.find('/')]
|
||||
services[service['name']] = service_type
|
||||
context = {
|
||||
"services": services,
|
||||
}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
class DeploymentHistoryTable(tables.DataTable):
|
||||
environment_name = tables.WrappingColumn(
|
||||
lambda d: d.description['name'],
|
||||
verbose_name=_('Environment'))
|
||||
logs = tables.Column(get_deployment_history_reports,
|
||||
verbose_name=_('Logs (Created, Message)'))
|
||||
services = tables.Column(get_deployment_history_services,
|
||||
verbose_name=_('Services (Name, Type)'))
|
||||
status = tables.Column(
|
||||
'state',
|
||||
verbose_name=_('Status'),
|
||||
status=True,
|
||||
display_choices=consts.DEPLOYMENT_STATUS_DISPLAY_CHOICES)
|
||||
|
||||
class Meta(object):
|
||||
name = 'deployment_history'
|
||||
verbose_name = _('Deployment History')
|
||||
row_actions = (ShowDeploymentDetails,)
|
||||
|
@ -37,6 +37,8 @@ urlpatterns = [
|
||||
views.ActionResultView.as_view(), name='action_result'),
|
||||
urls.url(r'^(?P<instance_id>[^/]+)/$',
|
||||
inst_view.DetailView.as_view(), name='detail'),
|
||||
urls.url(r'^deployment_history$', views.DeploymentHistoryView.as_view(),
|
||||
name='deployment_history'),
|
||||
urls.url(ENVIRONMENT_ID + r'/deployments/(?P<deployment_id>[^/]+)$',
|
||||
views.DeploymentDetailsView.as_view(), name='deployment_details'),
|
||||
]
|
||||
|
@ -190,6 +190,26 @@ class CreateEnvironmentView(views.ModalFormView):
|
||||
return reverse_lazy('horizon:app-catalog:environments:index')
|
||||
|
||||
|
||||
class DeploymentHistoryView(tables.DataTableView):
|
||||
table_class = env_tables.DeploymentHistoryTable
|
||||
template_name = 'environments/index.html'
|
||||
page_title = _("Deployment History")
|
||||
|
||||
def get_data(self):
|
||||
deployment_history = []
|
||||
try:
|
||||
deployment_history = api.deployment_history(self.request)
|
||||
except exc.HTTPUnauthorized:
|
||||
exceptions.handle(self.request)
|
||||
except exc.HTTPForbidden:
|
||||
redirect = reverse('horizon:app-catalog:environments:services',
|
||||
args=[self.environment_id])
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve deployment history.'),
|
||||
redirect=redirect)
|
||||
return deployment_history
|
||||
|
||||
|
||||
class DeploymentDetailsView(tabs.TabbedTableView):
|
||||
tab_group_class = env_tabs.DeploymentDetailsTabs
|
||||
table_class = env_tables.EnvConfigTable
|
||||
|
@ -0,0 +1,8 @@
|
||||
table.deployment_history_cell {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
table.deployment_history_cell td, table.deployment_history_cell th {
|
||||
border-top: none !important;
|
||||
padding-bottom: 2px !important;
|
||||
}
|
22
muranodashboard/templates/deployments/_cell_reports.html
Normal file
22
muranodashboard/templates/deployments/_cell_reports.html
Normal file
@ -0,0 +1,22 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load compress %}
|
||||
|
||||
{% block css %}
|
||||
{% compress css %}
|
||||
<link rel="stylesheet" href="{% static 'muranodashboard/css/deployments.css' %}">
|
||||
{% endcompress %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<table class="{% block table_css_classes %}table deployment_history_cell{% endblock %}">
|
||||
<thead></thead>
|
||||
<tbody>
|
||||
{% for report in reports %}
|
||||
<tr>
|
||||
<td>{{report.created | parse_isotime}} — {{report.text | linebreaksbr | urlize}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
22
muranodashboard/templates/deployments/_cell_services.html
Normal file
22
muranodashboard/templates/deployments/_cell_services.html
Normal file
@ -0,0 +1,22 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load compress %}
|
||||
|
||||
{% block css %}
|
||||
{% compress css %}
|
||||
<link rel="stylesheet" href="{% static 'muranodashboard/css/deployments.css' %}">
|
||||
{% endcompress %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<table class="{% block table_css_classes %}table deployment_history_cell{% endblock %}">
|
||||
<thead></thead>
|
||||
<tbody>
|
||||
{% for name, type in services.items %}
|
||||
<tr>
|
||||
<td>{{name}} — {{type | linebreaksbr}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
@ -1,11 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Environment Deployments" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "deployments/_page_header.html" %}
|
||||
{% endblock page_header %}
|
||||
{% block title %}{% trans "Environment Deployment History" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
@ -17,6 +17,8 @@ from django import http as django_http
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from horizon import tables as hz_tables
|
||||
|
||||
from muranoclient.common import exceptions as exc
|
||||
from muranodashboard.environments import consts
|
||||
from muranodashboard.environments import tables
|
||||
@ -830,3 +832,49 @@ class TestEnvConfigTable(testtools.TestCase):
|
||||
env_config_table = tables.EnvConfigTable(None)
|
||||
self.assertEqual('foo', env_config_table.get_object_id({
|
||||
'?': {'id': 'foo'}}))
|
||||
|
||||
|
||||
class TestDeploymentHistoryTable(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDeploymentHistoryTable, self).setUp()
|
||||
|
||||
deployment_history_table = tables.DeploymentHistoryTable(
|
||||
mock.Mock())
|
||||
columns = deployment_history_table.columns
|
||||
|
||||
self.assertIsInstance(columns['environment_name'],
|
||||
hz_tables.WrappingColumn)
|
||||
self.assertIsInstance(columns['logs'], hz_tables.Column)
|
||||
self.assertIsInstance(columns['services'], hz_tables.Column)
|
||||
self.assertIsInstance(columns['status'], hz_tables.Column)
|
||||
|
||||
self.assertEqual('Environment', str(columns['environment_name']))
|
||||
self.assertEqual('Logs (Created, Message)', str(columns['logs']))
|
||||
self.assertEqual('Services (Name, Type)', str(columns['services']))
|
||||
self.assertEqual('Status', str(columns['status']))
|
||||
self.assertTrue(columns['status'].status)
|
||||
self.assertEqual(consts.DEPLOYMENT_STATUS_DISPLAY_CHOICES,
|
||||
columns['status'].display_choices)
|
||||
|
||||
@mock.patch.object(tables, 'template')
|
||||
def test_get_deployment_history_services(self, mock_template):
|
||||
mock_template.loader.render_to_string.return_value = \
|
||||
mock.sentinel.rendered_template
|
||||
|
||||
test_description = {'services': [
|
||||
{'name': 'foo_service', '?': {'type': 'foo/bar'}},
|
||||
{'name': 'bar_service', '?': {'type': 'baz/qux'}}
|
||||
]}
|
||||
mock_deployment = mock.Mock(description=test_description)
|
||||
|
||||
result = tables.get_deployment_history_services(mock_deployment)
|
||||
self.assertEqual(mock.sentinel.rendered_template, result)
|
||||
|
||||
expected_services = {
|
||||
'services': {
|
||||
'bar_service': 'baz', 'foo_service': 'foo'
|
||||
}
|
||||
}
|
||||
mock_template.loader.render_to_string.assert_called_once_with(
|
||||
'deployments/_cell_services.html', expected_services)
|
||||
|
@ -566,3 +566,56 @@ class TestActionResultView(testtools.TestCase):
|
||||
|
||||
mock_api_utils.muranoclient().actions.get_result.\
|
||||
assert_called_once_with('foo_env_id', 'foo_task_id')
|
||||
|
||||
|
||||
class TestDeploymentHistoryView(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDeploymentHistoryView, self).setUp()
|
||||
self.deployment_history_view = views.DeploymentHistoryView()
|
||||
|
||||
self.mock_request = mock.Mock()
|
||||
self.deployment_history_view.request = self.mock_request
|
||||
self.deployment_history_view.environment_id = mock.sentinel.env_id
|
||||
|
||||
self.assertEqual(env_tables.DeploymentHistoryTable,
|
||||
self.deployment_history_view.table_class)
|
||||
self.assertEqual('environments/index.html',
|
||||
self.deployment_history_view.template_name)
|
||||
self.assertEqual(_('Deployment History'),
|
||||
self.deployment_history_view.page_title)
|
||||
|
||||
@mock.patch.object(views, 'api', autospec=True)
|
||||
def test_get_data(self, mock_env_api):
|
||||
mock_env_api.deployment_history.return_value = \
|
||||
[mock.sentinel.deployment_history]
|
||||
|
||||
result = self.deployment_history_view.get_data()
|
||||
self.assertEqual([mock.sentinel.deployment_history], result)
|
||||
|
||||
@mock.patch.object(views, 'exceptions', autospec=True)
|
||||
@mock.patch.object(views, 'api', autospec=True)
|
||||
def test_get_data_except_http_unauthorized(self, mock_env_api,
|
||||
mock_exceptions):
|
||||
mock_env_api.deployment_history.side_effect = \
|
||||
exc.HTTPUnauthorized
|
||||
|
||||
self.assertEqual([], self.deployment_history_view.get_data())
|
||||
mock_exceptions.handle.assert_called_once_with(self.mock_request)
|
||||
|
||||
@mock.patch.object(views, 'exceptions', autospec=True)
|
||||
@mock.patch.object(views, 'reverse', autospec=True)
|
||||
@mock.patch.object(views, 'api', autospec=True)
|
||||
def test_get_data_except_http_forbidden(self, mock_env_api, mock_reverse,
|
||||
mock_exceptions):
|
||||
mock_env_api.deployment_history.side_effect = \
|
||||
exc.HTTPForbidden
|
||||
mock_reverse.return_value = mock.sentinel.redirect_url
|
||||
|
||||
self.assertEqual([], self.deployment_history_view.get_data())
|
||||
mock_reverse.assert_called_once_with(
|
||||
'horizon:app-catalog:environments:services',
|
||||
args=[mock.sentinel.env_id])
|
||||
mock_exceptions.handle.assert_called_once_with(
|
||||
self.mock_request, _('Unable to retrieve deployment history.'),
|
||||
redirect=mock.sentinel.redirect_url)
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
A new button, called "Deployment History", has been added to the
|
||||
Environments > Applications view. When clicked, the deployment history view
|
||||
is loaded, which shows deployments for all environments for the current
|
||||
project (tenant).
|
Loading…
x
Reference in New Issue
Block a user