diff --git a/openstack_dashboard/conf/heat_policy.json b/openstack_dashboard/conf/heat_policy.json index 2e34982122..eb5fab695c 100644 --- a/openstack_dashboard/conf/heat_policy.json +++ b/openstack_dashboard/conf/heat_policy.json @@ -45,6 +45,8 @@ "stacks:generate_template": "rule:deny_stack_user", "stacks:index": "rule:deny_stack_user", "stacks:list_resource_types": "rule:deny_stack_user", + "stacks:list_template_versions": "rule:deny_stack_user", + "stacks:list_template_functions": "rule:deny_stack_user", "stacks:lookup": "rule:deny_stack_user", "stacks:resource_schema": "rule:deny_stack_user", "stacks:show": "rule:deny_stack_user", diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/__init__.py b/openstack_dashboard/dashboards/project/stacks/template_versions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/panel.py b/openstack_dashboard/dashboards/project/stacks/template_versions/panel.py new file mode 100644 index 0000000000..193da6e486 --- /dev/null +++ b/openstack_dashboard/dashboards/project/stacks/template_versions/panel.py @@ -0,0 +1,22 @@ +# 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 TemplateVersions(horizon.Panel): + name = _("Template Versions") + slug = "stacks.template_versions" + permissions = ('openstack.services.orchestration',) diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/tables.py b/openstack_dashboard/dashboards/project/stacks/template_versions/tables.py new file mode 100644 index 0000000000..9fbef85997 --- /dev/null +++ b/openstack_dashboard/dashboards/project/stacks/template_versions/tables.py @@ -0,0 +1,47 @@ +# 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.template import defaultfilters as filters +from django.utils.translation import ugettext_lazy as _ + +from horizon import tables + + +class TemplateVersionsTable(tables.DataTable): + version = tables.Column( + "version", + verbose_name=_("Version"), + link="horizon:project:stacks.template_versions:details",) + type = tables.Column( + "type", + verbose_name=_("Type"), + filters=(filters.upper,)) + + def get_object_id(self, template_versions): + return template_versions.version + + class Meta(object): + name = "template_versions" + verbose_name = _("Template Versions") + + +class TemplateFunctionsTable(tables.DataTable): + functions = tables.Column('functions', verbose_name=_("Function")) + description = tables.Column('description', verbose_name=_("Description")) + + def get_object_id(self, template_functions): + return template_functions.functions + + class Meta(object): + name = "template_functions" + verbose_name = _("Template Functions") diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/tabs.py b/openstack_dashboard/dashboards/project/stacks/template_versions/tabs.py new file mode 100644 index 0000000000..9cb90968c3 --- /dev/null +++ b/openstack_dashboard/dashboards/project/stacks/template_versions/tabs.py @@ -0,0 +1,51 @@ +# 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 messages +from horizon import tabs +from openstack_dashboard import api +from openstack_dashboard import policy + +from openstack_dashboard.dashboards.project.stacks.template_versions \ + import tables as project_tables + + +class TemplateFunctionsTab(tabs.Tab): + name = _("Template Functions") + slug = "template_functions" + template_name = "project/stacks.template_versions/_details.html" + preload = False + + def allowed(self, request): + return policy.check( + (("orchestration", "stacks:list_template_functions"),), + request) + + def get_context_data(self, request): + template_version = self.tab_group.kwargs['template_version'] + try: + template_functions = api.heat.template_function_list( + self.request, template_version) + except Exception: + template_functions = [] + messages.error(request, _('Unable to get functions for template ' + 'version "%s".') % template_version) + return {"table": project_tables.TemplateFunctionsTable( + request, data=template_functions), } + + +class TemplateVersionDetailsTabs(tabs.TabGroup): + slug = "template_version_details" + tabs = (TemplateFunctionsTab,) diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/_details.html b/openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/_details.html new file mode 100644 index 0000000000..9976f88dd9 --- /dev/null +++ b/openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/_details.html @@ -0,0 +1,3 @@ +{% load i18n %} + +{{ table.render }} diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/index.html b/openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/index.html new file mode 100644 index 0000000000..0a39d4a5e8 --- /dev/null +++ b/openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/index.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Template Versions" %}{% endblock %} + +{% block main %} + {{ table.render }} +{% endblock %} diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/tests.py b/openstack_dashboard/dashboards/project/stacks/template_versions/tests.py new file mode 100644 index 0000000000..064b134aa6 --- /dev/null +++ b/openstack_dashboard/dashboards/project/stacks/template_versions/tests.py @@ -0,0 +1,79 @@ +# 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 # noqa + +from openstack_dashboard import api +from openstack_dashboard.test import helpers as test + + +class TemplateVersionsTests(test.TestCase): + INDEX_URL = reverse('horizon:project:stacks.template_versions:index') + + @test.create_stubs({api.heat: ('template_version_list',)}) + def test_index(self): + api.heat.template_version_list( + IsA(http.HttpRequest)).AndReturn(self.template_versions.list()) + self.mox.ReplayAll() + + res = self.client.get(self.INDEX_URL) + self.assertTemplateUsed( + res, 'project/stacks.template_versions/index.html') + self.assertContains(res, 'HeatTemplateFormatVersion.2012-12-12') + + @test.create_stubs({api.heat: ('template_version_list',)}) + def test_index_exception(self): + api.heat.template_version_list( + IsA(http.HttpRequest)).AndRaise(self.exceptions.heat) + self.mox.ReplayAll() + + res = self.client.get(self.INDEX_URL) + self.assertTemplateUsed( + res, 'project/stacks.template_versions/index.html') + self.assertEqual(len(res.context['table'].data), 0) + self.assertMessageCount(res, error=1) + + @test.create_stubs({api.heat: ('template_function_list',)}) + def test_detail_view(self): + t_version = self.template_versions.first().version + t_functions = self.template_functions.list() + + api.heat.template_function_list( + IsA(http.HttpRequest), t_version).AndReturn(t_functions) + self.mox.ReplayAll() + + url = reverse('horizon:project:stacks.template_versions:details', + args=[t_version]) + res = self.client.get(url) + + self.assertTemplateUsed(res, 'horizon/common/_detail.html') + self.assertNoMessages() + + @test.create_stubs({api.heat: ('template_function_list',)}) + def test_detail_view_with_exception(self): + t_version = self.template_versions.first().version + + api.heat.template_function_list( + IsA(http.HttpRequest), t_version).AndRaise(self.exceptions.heat) + self.mox.ReplayAll() + + url = reverse('horizon:project:stacks.template_versions:details', + args=[t_version]) + res = self.client.get(url) + + self.assertTemplateUsed(res, 'horizon/common/_detail.html') + self.assertEqual(len(res.context['table'].data), 0) + self.assertMessageCount(res, error=1) diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/urls.py b/openstack_dashboard/dashboards/project/stacks/template_versions/urls.py new file mode 100644 index 0000000000..5e2bbc2e58 --- /dev/null +++ b/openstack_dashboard/dashboards/project/stacks/template_versions/urls.py @@ -0,0 +1,24 @@ +# 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 openstack_dashboard.dashboards.project.stacks.template_versions \ + import views + + +urlpatterns = [ + url(r'^$', views.TemplateVersionsView.as_view(), name='index'), + url(r'^(?P[^/]+)/$', + views.DetailView.as_view(), name='details'), +] diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/views.py b/openstack_dashboard/dashboards/project/stacks/template_versions/views.py new file mode 100644 index 0000000000..22eabd77fa --- /dev/null +++ b/openstack_dashboard/dashboards/project/stacks/template_versions/views.py @@ -0,0 +1,61 @@ +# 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.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import tables +from horizon import tabs + +from openstack_dashboard import api +import openstack_dashboard.dashboards.project.stacks.template_versions.tables \ + as project_tables +import openstack_dashboard.dashboards.project.stacks.template_versions.tabs \ + as project_tabs + + +class TemplateVersionsView(tables.DataTableView): + table_class = project_tables.TemplateVersionsTable + template_name = 'project/stacks.template_versions/index.html' + page_title = _("Template Versions") + + def get_data(self): + try: + template_versions = sorted( + api.heat.template_version_list(self.request), + key=lambda template_version: template_version.version) + except Exception: + template_versions = [] + msg = _('Unable to retrieve template versions.') + exceptions.handle(self.request, msg) + return template_versions + + +class DetailView(tabs.TabView): + tab_group_class = project_tabs.TemplateVersionDetailsTabs + template_name = 'horizon/common/_detail.html' + page_title = "{{ template_version }}" + + def get_template_version(self, request, **kwargs): + try: + template_functions = api.heat.template_function_list( + request, kwargs['template_version']) + return template_functions + except Exception: + msg = _('Unable to retrieve template functions.') + exceptions.handle(request, msg, redirect=self.get_redirect_url()) + + @staticmethod + def get_redirect_url(): + return reverse('horizon:project:stacks.template_versions:index') diff --git a/openstack_dashboard/enabled/_1640_project_template_versions_panel.py b/openstack_dashboard/enabled/_1640_project_template_versions_panel.py new file mode 100644 index 0000000000..f31e040418 --- /dev/null +++ b/openstack_dashboard/enabled/_1640_project_template_versions_panel.py @@ -0,0 +1,10 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'stacks.template_versions' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'project' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'orchestration' + +# Python panel class of the PANEL to be added. +ADD_PANEL = ('openstack_dashboard.dashboards.project.' + 'stacks.template_versions.panel.TemplateVersions') diff --git a/openstack_dashboard/test/test_data/exceptions.py b/openstack_dashboard/test/test_data/exceptions.py index 2d40aa4c10..9a33e0b8a8 100644 --- a/openstack_dashboard/test/test_data/exceptions.py +++ b/openstack_dashboard/test/test_data/exceptions.py @@ -15,6 +15,7 @@ import ceilometerclient.exc as ceilometer_exceptions from cinderclient import exceptions as cinder_exceptions import glanceclient.exc as glance_exceptions +import heatclient.exc as heat_exceptions from keystoneclient import exceptions as keystone_exceptions from neutronclient.common import exceptions as neutron_exceptions from novaclient import exceptions as nova_exceptions @@ -84,3 +85,6 @@ def data(TEST): cinder_exception = cinder_exceptions.BadRequest TEST.exceptions.cinder = create_stubbed_exception(cinder_exception) + + heat_exception = heat_exceptions.HTTPException + TEST.exceptions.heat = create_stubbed_exception(heat_exception)