From 9f72d2fb199fa24d3714dd076a8af52f3682a77b Mon Sep 17 00:00:00 2001 From: Felipe Monteiro Date: Sun, 8 Jan 2017 01:38:42 +0000 Subject: [PATCH] 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 --- muranodashboard/environments/api.py | 14 +++++ muranodashboard/environments/tables.py | 63 ++++++++++++++++++- muranodashboard/environments/urls.py | 2 + muranodashboard/environments/views.py | 20 ++++++ .../muranodashboard/css/deployments.css | 8 +++ .../templates/deployments/_cell_reports.html | 22 +++++++ .../templates/deployments/_cell_services.html | 22 +++++++ .../templates/deployments/index.html | 8 +-- .../tests/unit/environments/test_tables.py | 48 ++++++++++++++ .../tests/unit/environments/test_views.py | 53 ++++++++++++++++ ...history-all-env-view-7610fd4301604b45.yaml | 7 +++ 11 files changed, 260 insertions(+), 7 deletions(-) create mode 100644 muranodashboard/static/muranodashboard/css/deployments.css create mode 100644 muranodashboard/templates/deployments/_cell_reports.html create mode 100644 muranodashboard/templates/deployments/_cell_services.html create mode 100644 releasenotes/notes/create-deployment-history-all-env-view-7610fd4301604b45.yaml diff --git a/muranodashboard/environments/api.py b/muranodashboard/environments/api.py index be8d45345..4eb5c89dc 100644 --- a/muranodashboard/environments/api.py +++ b/muranodashboard/environments/api.py @@ -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, diff --git a/muranodashboard/environments/tables.py b/muranodashboard/environments/tables.py index 143a1c24e..fa457e819 100644 --- a/muranodashboard/environments/tables.py +++ b/muranodashboard/environments/tables.py @@ -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,) diff --git a/muranodashboard/environments/urls.py b/muranodashboard/environments/urls.py index cdd890fb3..f00fca0df 100644 --- a/muranodashboard/environments/urls.py +++ b/muranodashboard/environments/urls.py @@ -37,6 +37,8 @@ urlpatterns = [ views.ActionResultView.as_view(), name='action_result'), urls.url(r'^(?P[^/]+)/$', 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[^/]+)$', views.DeploymentDetailsView.as_view(), name='deployment_details'), ] diff --git a/muranodashboard/environments/views.py b/muranodashboard/environments/views.py index 7674b7062..c70d615f8 100644 --- a/muranodashboard/environments/views.py +++ b/muranodashboard/environments/views.py @@ -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 diff --git a/muranodashboard/static/muranodashboard/css/deployments.css b/muranodashboard/static/muranodashboard/css/deployments.css new file mode 100644 index 000000000..57f87e58e --- /dev/null +++ b/muranodashboard/static/muranodashboard/css/deployments.css @@ -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; +} \ No newline at end of file diff --git a/muranodashboard/templates/deployments/_cell_reports.html b/muranodashboard/templates/deployments/_cell_reports.html new file mode 100644 index 000000000..24d1aee2f --- /dev/null +++ b/muranodashboard/templates/deployments/_cell_reports.html @@ -0,0 +1,22 @@ +{% load i18n %} +{% load static %} +{% load compress %} + +{% block css %} + {% compress css %} + + {% endcompress %} +{% endblock %} + +{% block main %} + + + +{% for report in reports %} + + + +{% endfor %} + +
{{report.created | parse_isotime}} — {{report.text | linebreaksbr | urlize}}
+{% endblock %} \ No newline at end of file diff --git a/muranodashboard/templates/deployments/_cell_services.html b/muranodashboard/templates/deployments/_cell_services.html new file mode 100644 index 000000000..de6c416b6 --- /dev/null +++ b/muranodashboard/templates/deployments/_cell_services.html @@ -0,0 +1,22 @@ +{% load i18n %} +{% load static %} +{% load compress %} + +{% block css %} + {% compress css %} + + {% endcompress %} +{% endblock %} + +{% block main %} + + + +{% for name, type in services.items %} + + + +{% endfor %} + +
{{name}} — {{type | linebreaksbr}}
+{% endblock %} \ No newline at end of file diff --git a/muranodashboard/templates/deployments/index.html b/muranodashboard/templates/deployments/index.html index 353951c32..db5ad4c8c 100644 --- a/muranodashboard/templates/deployments/index.html +++ b/muranodashboard/templates/deployments/index.html @@ -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 %} \ No newline at end of file diff --git a/muranodashboard/tests/unit/environments/test_tables.py b/muranodashboard/tests/unit/environments/test_tables.py index baf04dc2a..57d2745e7 100644 --- a/muranodashboard/tests/unit/environments/test_tables.py +++ b/muranodashboard/tests/unit/environments/test_tables.py @@ -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) diff --git a/muranodashboard/tests/unit/environments/test_views.py b/muranodashboard/tests/unit/environments/test_views.py index de5f62a65..2b2b0a585 100644 --- a/muranodashboard/tests/unit/environments/test_views.py +++ b/muranodashboard/tests/unit/environments/test_views.py @@ -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) diff --git a/releasenotes/notes/create-deployment-history-all-env-view-7610fd4301604b45.yaml b/releasenotes/notes/create-deployment-history-all-env-view-7610fd4301604b45.yaml new file mode 100644 index 000000000..da4193fad --- /dev/null +++ b/releasenotes/notes/create-deployment-history-all-env-view-7610fd4301604b45.yaml @@ -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).