From 222891cf83dc9ef76884eb14382b02854fac93c3 Mon Sep 17 00:00:00 2001 From: Tzu-Mainn Chen Date: Tue, 5 Aug 2014 05:48:50 +0200 Subject: [PATCH] Added Overview panel The overview panel is a combination of the former overcloud and plan panels. The assumption here is that a plan already exists; different view states will be determined by whether an associated stack exists. Future enhancements will include a reorganization of the templates used and updating the mock data to simulate the creation of a Heat stack Change-Id: I37d0943d3dbe0cb2849af82a459bc5b2af17881c --- tuskar_ui/infrastructure/dashboard.py | 5 +- tuskar_ui/infrastructure/overcloud/panel.py | 27 --- tuskar_ui/infrastructure/overcloud/tables.py | 35 ---- tuskar_ui/infrastructure/overcloud/tabs.py | 161 ------------------ tuskar_ui/infrastructure/overcloud/urls.py | 31 ---- tuskar_ui/infrastructure/overcloud/views.py | 144 ---------------- .../{overcloud => overview}/__init__.py | 0 .../{overcloud => overview}/forms.py | 0 .../{plans => overview}/panel.py | 9 +- .../templates/overview/_plan_overview.html} | 0 .../templates/overview/_role_nodes.html} | 0 .../overview}/_undeploy_confirmation.html | 4 +- .../overview}/_undeploy_in_progress.html | 2 +- .../templates/overview/index.html} | 12 +- .../overview}/undeploy_confirmation.html | 2 +- .../{overcloud => overview}/tests.py | 121 ++++--------- .../{plans => overview}/urls.py | 8 +- tuskar_ui/infrastructure/overview/views.py | 157 +++++++++++++++++ tuskar_ui/infrastructure/plans/__init__.py | 0 tuskar_ui/infrastructure/plans/forms.py | 66 ------- tuskar_ui/infrastructure/plans/tables.py | 35 ---- .../templates/plans/create_configuration.html | 9 - .../templates/plans/create_overview.html | 16 -- .../templates/plans/scale_node_counts.html | 5 - tuskar_ui/infrastructure/plans/tests.py | 65 ------- tuskar_ui/infrastructure/plans/views.py | 91 ---------- .../plans/workflows/__init__.py | 0 .../infrastructure/plans/workflows/create.py | 51 ------ .../plans/workflows/create_configuration.py | 89 ---------- .../plans/workflows/create_overview.py | 53 ------ .../infrastructure/plans/workflows/scale.py | 47 ----- .../plans/workflows/scale_node_counts.py | 35 ---- 32 files changed, 213 insertions(+), 1067 deletions(-) delete mode 100644 tuskar_ui/infrastructure/overcloud/panel.py delete mode 100644 tuskar_ui/infrastructure/overcloud/tables.py delete mode 100644 tuskar_ui/infrastructure/overcloud/tabs.py delete mode 100644 tuskar_ui/infrastructure/overcloud/urls.py delete mode 100644 tuskar_ui/infrastructure/overcloud/views.py rename tuskar_ui/infrastructure/{overcloud => overview}/__init__.py (100%) rename tuskar_ui/infrastructure/{overcloud => overview}/forms.py (100%) rename tuskar_ui/infrastructure/{plans => overview}/panel.py (85%) rename tuskar_ui/infrastructure/{overcloud/templates/overcloud/_detail_overview.html => overview/templates/overview/_plan_overview.html} (100%) rename tuskar_ui/infrastructure/{plans/templates/plans/node_counts.html => overview/templates/overview/_role_nodes.html} (100%) rename tuskar_ui/infrastructure/{overcloud/templates/overcloud => overview/templates/overview}/_undeploy_confirmation.html (74%) rename tuskar_ui/infrastructure/{overcloud/templates/overcloud => overview/templates/overview}/_undeploy_in_progress.html (92%) rename tuskar_ui/infrastructure/{overcloud/templates/overcloud/detail.html => overview/templates/overview/index.html} (65%) rename tuskar_ui/infrastructure/{overcloud/templates/overcloud => overview/templates/overview}/undeploy_confirmation.html (80%) rename tuskar_ui/infrastructure/{overcloud => overview}/tests.py (50%) rename tuskar_ui/infrastructure/{plans => overview}/urls.py (77%) create mode 100644 tuskar_ui/infrastructure/overview/views.py delete mode 100644 tuskar_ui/infrastructure/plans/__init__.py delete mode 100644 tuskar_ui/infrastructure/plans/forms.py delete mode 100644 tuskar_ui/infrastructure/plans/tables.py delete mode 100644 tuskar_ui/infrastructure/plans/templates/plans/create_configuration.html delete mode 100644 tuskar_ui/infrastructure/plans/templates/plans/create_overview.html delete mode 100644 tuskar_ui/infrastructure/plans/templates/plans/scale_node_counts.html delete mode 100644 tuskar_ui/infrastructure/plans/tests.py delete mode 100644 tuskar_ui/infrastructure/plans/views.py delete mode 100644 tuskar_ui/infrastructure/plans/workflows/__init__.py delete mode 100644 tuskar_ui/infrastructure/plans/workflows/create.py delete mode 100644 tuskar_ui/infrastructure/plans/workflows/create_configuration.py delete mode 100644 tuskar_ui/infrastructure/plans/workflows/create_overview.py delete mode 100644 tuskar_ui/infrastructure/plans/workflows/scale.py delete mode 100644 tuskar_ui/infrastructure/plans/workflows/scale_node_counts.py diff --git a/tuskar_ui/infrastructure/dashboard.py b/tuskar_ui/infrastructure/dashboard.py index d1a9368c7..acac4a89d 100644 --- a/tuskar_ui/infrastructure/dashboard.py +++ b/tuskar_ui/infrastructure/dashboard.py @@ -20,8 +20,7 @@ class BasePanels(horizon.PanelGroup): slug = "infrastructure" name = _("Infrastructure") panels = ( - 'overcloud', - 'plans', + 'overview', 'parameters', 'roles', 'nodes', @@ -37,7 +36,7 @@ class Infrastructure(horizon.Dashboard): panels = ( BasePanels, ) - default_panel = 'overcloud' + default_panel = 'overview' permissions = ('openstack.roles.admin',) diff --git a/tuskar_ui/infrastructure/overcloud/panel.py b/tuskar_ui/infrastructure/overcloud/panel.py deleted file mode 100644 index c9b6c9c74..000000000 --- a/tuskar_ui/infrastructure/overcloud/panel.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- 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 Overcloud(horizon.Panel): - name = _("OpenStack Deployment") - slug = "overcloud" - - -dashboard.Infrastructure.register(Overcloud) diff --git a/tuskar_ui/infrastructure/overcloud/tables.py b/tuskar_ui/infrastructure/overcloud/tables.py deleted file mode 100644 index 2be5d6f91..000000000 --- a/tuskar_ui/infrastructure/overcloud/tables.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- 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 - - -class ConfigurationTable(tables.DataTable): - - key = tables.Column(lambda parameter: parameter[0], - verbose_name=_("Attribute Name")) - value = tables.Column(lambda parameter: parameter[1], - verbose_name=_("Attribute Value")) - - class Meta: - name = "configuration" - verbose_name = _("Configuration") - multi_select = False - table_actions = () - row_actions = () - - def get_object_id(self, datum): - return datum[0] diff --git a/tuskar_ui/infrastructure/overcloud/tabs.py b/tuskar_ui/infrastructure/overcloud/tabs.py deleted file mode 100644 index 0e4e580d5..000000000 --- a/tuskar_ui/infrastructure/overcloud/tabs.py +++ /dev/null @@ -1,161 +0,0 @@ -# Copyright 2012 Nebula, Inc. -# -# 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 heatclient -from horizon import tabs - -from tuskar_ui.infrastructure.overcloud import tables -from tuskar_ui.utils import utils - - -def _get_role_data(stack, role): - """Gathers data about a single deployment role from the related Overcloud - and OvercloudRole objects, and presents it in the form convenient for use - from the template. - - :param stack: Stack object - :type stack: tuskar_ui.api.heat.Stack - :param role: Role object - :type role: tuskar_ui.api.tuskar.OvercloudRole - :return: dict with information about the role, to be used by template - :rtype: dict - """ - resources = stack.resources_by_role(role, with_joins=True) - nodes = [r.node for r in resources] - node_count = len(nodes) - - data = { - 'role': role, - 'name': role.name, - 'total_node_count': node_count, - } - deployed_node_count = 0 - deploying_node_count = 0 - error_node_count = 0 - waiting_node_count = node_count - - if nodes: - deployed_node_count = sum(1 for node in nodes - if node.instance.status == 'ACTIVE') - deploying_node_count = sum(1 for node in nodes - if node.instance.status == 'BUILD') - error_node_count = sum(1 for node in nodes - if node.instance.status == 'ERROR') - waiting_node_count = (node_count - deployed_node_count - - deploying_node_count - error_node_count) - - data.update({ - 'deployed_node_count': deployed_node_count, - 'deploying_node_count': deploying_node_count, - 'waiting_node_count': waiting_node_count, - 'error_node_count': error_node_count, - }) - # TODO(rdopieralski) get this from ceilometer - # data['capacity'] = 20 - return data - - -class OverviewTab(tabs.Tab): - name = _("Overview") - slug = "overview" - template_name = "infrastructure/overcloud/_detail_overview.html" - preload = False - - def get_context_data(self, request, **kwargs): - stack = self.tab_group.kwargs['stack'] - roles = stack.plan.role_list - role_data = [_get_role_data(stack, role) for role in roles] - total = sum(d['total_node_count'] for d in role_data) - progress = 100 * sum(d.get('deployed_node_count', 0) - for d in role_data) // (total or 1) - - events = stack.events - last_failed_events = [e for e in events - if e.resource_status == 'CREATE_FAILED'][-3:] - - return { - 'stack': stack, - 'plan': stack.plan, - 'roles': role_data, - 'progress': max(5, progress), - 'dashboard_urls': stack.dashboard_urls, - 'last_failed_events': last_failed_events, - } - - -class UndeployInProgressTab(tabs.Tab): - name = _("Overview") - slug = "undeploy_in_progress_tab" - template_name = "infrastructure/overcloud/_undeploy_in_progress.html" - preload = False - - def get_context_data(self, request, **kwargs): - stack = self.tab_group.kwargs['stack'] - - # TODO(lsmola) since at this point we don't have total number of nodes - # we will hack this around, till API can show this information. So it - # will actually show progress like the total number is 10, or it will - # show progress of 5%. Ugly, but workable. - total_num_nodes_count = 10 - - try: - resources_count = len( - stack.resources(with_joins=False)) - except heatclient.exc.HTTPNotFound: - # Immediately after undeploying has started, heat returns this - # exception so we can take it as kind of init of undeploying. - resources_count = total_num_nodes_count - - # TODO(lsmola) same as hack above - total_num_nodes_count = max(resources_count, total_num_nodes_count) - - delete_progress = max( - 5, 100 * (total_num_nodes_count - resources_count)) - - events = stack.events - last_failed_events = [e for e in events - if e.resource_status == 'DELETE_FAILED'][-3:] - return { - 'stack': stack, - 'plan': stack.plan, - 'progress': delete_progress, - 'last_failed_events': last_failed_events, - } - - -class ConfigurationTab(tabs.TableTab): - table_classes = (tables.ConfigurationTable,) - name = _("Configuration") - slug = "configuration" - template_name = "horizon/common/_detail_table.html" - preload = False - - def get_configuration_data(self): - stack = self.tab_group.kwargs['stack'] - - return [(utils.de_camel_case(key), value) for key, value in - stack.parameters.items()] - - -class UndeployInProgressTabs(tabs.TabGroup): - slug = "undeploy_in_progress" - tabs = (UndeployInProgressTab,) - sticky = True - - -class DetailTabs(tabs.TabGroup): - slug = "detail" - tabs = (OverviewTab, ConfigurationTab,) - sticky = True diff --git a/tuskar_ui/infrastructure/overcloud/urls.py b/tuskar_ui/infrastructure/overcloud/urls.py deleted file mode 100644 index d2d88c782..000000000 --- a/tuskar_ui/infrastructure/overcloud/urls.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- 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.overcloud import views - - -urlpatterns = urls.patterns( - '', - urls.url(r'^$', views.IndexView.as_view(), name='index'), - urls.url(r'^(?P[^/]+)/undeploy-in-progress$', - views.UndeployInProgressView.as_view(), - name='undeploy_in_progress'), - urls.url(r'^(?P[^/]+)/$', views.DetailView.as_view(), - name='detail'), - 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 deleted file mode 100644 index 1d3048374..000000000 --- a/tuskar_ui/infrastructure/overcloud/views.py +++ /dev/null @@ -1,144 +0,0 @@ -# -*- 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 django.utils.translation import ugettext_lazy as _ -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 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 tabs - - -INDEX_URL = 'horizon:infrastructure:overcloud:index' -DETAIL_URL = 'horizon:infrastructure:overcloud:detail' -PLAN_CREATE_URL = 'horizon:infrastructure:plans:create' -UNDEPLOY_IN_PROGRESS_URL = ( - 'horizon:infrastructure:overcloud:undeploy_in_progress') - - -class StackMixin(object): - @memoized.memoized - def get_stack(self, redirect=None): - if redirect is None: - redirect = reverse(INDEX_URL) - stack_id = self.kwargs['stack_id'] - stack = api.heat.Stack.get(self.request, stack_id, - _error_redirect=redirect) - return stack - - -class IndexView(base_views.RedirectView): - permanent = False - - def get_redirect_url(self): - plan = api.tuskar.OvercloudPlan.get_the_plan(self.request) - - redirect = reverse(PLAN_CREATE_URL) - if plan is not None: - stacks = api.heat.Stack.list(self.request) - for stack in stacks: - if stack.plan.id == plan.id: - break - else: - stack = None - - if stack is not None: - if stack.is_deleting or stack.is_delete_failed: - redirect = reverse(UNDEPLOY_IN_PROGRESS_URL, - args=(stack.id,)) - else: - redirect = reverse(DETAIL_URL, - args=(stack.id,)) - return redirect - - -class DetailView(horizon_tabs.TabView, StackMixin): - tab_group_class = tabs.DetailTabs - template_name = 'infrastructure/overcloud/detail.html' - - def get_tabs(self, request, **kwargs): - stack = self.get_stack() - return self.tab_group_class(request, stack=stack, **kwargs) - - def get_context_data(self, **kwargs): - context = super(DetailView, self).get_context_data(**kwargs) - context['stack'] = self.get_stack() - context['plan'] = self.get_stack().plan - return context - - -class UndeployConfirmationView(horizon.forms.ModalFormView): - form_class = forms.UndeployOvercloud - template_name = 'infrastructure/overcloud/undeploy_confirmation.html' - - def get_success_url(self): - return reverse(INDEX_URL) - - def get_context_data(self, **kwargs): - context = super(UndeployConfirmationView, - self).get_context_data(**kwargs) - context['stack_id'] = self.kwargs['stack_id'] - return context - - def get_initial(self, **kwargs): - initial = super(UndeployConfirmationView, self).get_initial(**kwargs) - initial['stack_id'] = self.kwargs['stack_id'] - return initial - - -class UndeployInProgressView(horizon_tabs.TabView, StackMixin, ): - tab_group_class = tabs.UndeployInProgressTabs - template_name = 'infrastructure/overcloud/detail.html' - - def get_stack_or_redirect(self): - plan = api.tuskar.OvercloudPlan.get_the_plan(self.request) - stack = None - - if plan is not None: - stack = None - stacks = api.heat.Stack.list(self.request) - for s in stacks: - if s.plan.id == plan.id: - stack = s - break - - if stack is None: - redirect = reverse(INDEX_URL) - messages.success(self.request, - _("Undeploying of the Overcloud has finished.")) - raise horizon_exceptions.Http302(redirect) - elif stack.is_deleting or stack.is_delete_failed: - return stack - else: - messages.error(self.request, - _("Overcloud is not being undeployed.")) - redirect = reverse(DETAIL_URL, - args=(stack.id,)) - raise horizon_exceptions.Http302(redirect) - - def get_tabs(self, request, **kwargs): - stack = self.get_stack_or_redirect() - return self.tab_group_class(request, stack=stack, **kwargs) - - def get_context_data(self, **kwargs): - context = super(UndeployInProgressView, - self).get_context_data(**kwargs) - context['stack'] = self.get_stack_or_redirect() - return context diff --git a/tuskar_ui/infrastructure/overcloud/__init__.py b/tuskar_ui/infrastructure/overview/__init__.py similarity index 100% rename from tuskar_ui/infrastructure/overcloud/__init__.py rename to tuskar_ui/infrastructure/overview/__init__.py diff --git a/tuskar_ui/infrastructure/overcloud/forms.py b/tuskar_ui/infrastructure/overview/forms.py similarity index 100% rename from tuskar_ui/infrastructure/overcloud/forms.py rename to tuskar_ui/infrastructure/overview/forms.py diff --git a/tuskar_ui/infrastructure/plans/panel.py b/tuskar_ui/infrastructure/overview/panel.py similarity index 85% rename from tuskar_ui/infrastructure/plans/panel.py rename to tuskar_ui/infrastructure/overview/panel.py index 79bee89ee..fb5e303f2 100644 --- a/tuskar_ui/infrastructure/plans/panel.py +++ b/tuskar_ui/infrastructure/overview/panel.py @@ -19,10 +19,9 @@ import horizon from tuskar_ui.infrastructure import dashboard -class Plans(horizon.Panel): - name = _("Plans") - slug = "plans" - nav = False +class Overview(horizon.Panel): + name = _("Overview") + slug = "overview" -dashboard.Infrastructure.register(Plans) +dashboard.Infrastructure.register(Overview) diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_detail_overview.html b/tuskar_ui/infrastructure/overview/templates/overview/_plan_overview.html similarity index 100% rename from tuskar_ui/infrastructure/overcloud/templates/overcloud/_detail_overview.html rename to tuskar_ui/infrastructure/overview/templates/overview/_plan_overview.html diff --git a/tuskar_ui/infrastructure/plans/templates/plans/node_counts.html b/tuskar_ui/infrastructure/overview/templates/overview/_role_nodes.html similarity index 100% rename from tuskar_ui/infrastructure/plans/templates/plans/node_counts.html rename to tuskar_ui/infrastructure/overview/templates/overview/_role_nodes.html diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_undeploy_confirmation.html b/tuskar_ui/infrastructure/overview/templates/overview/_undeploy_confirmation.html similarity index 74% rename from tuskar_ui/infrastructure/overcloud/templates/overcloud/_undeploy_confirmation.html rename to tuskar_ui/infrastructure/overview/templates/overview/_undeploy_confirmation.html index a0f29085d..aeb14d63f 100644 --- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_undeploy_confirmation.html +++ b/tuskar_ui/infrastructure/overview/templates/overview/_undeploy_confirmation.html @@ -3,7 +3,7 @@ {% load url from future %} {% block form_id %}provision_form{% endblock %} -{% block form_action %}{% url 'horizon:infrastructure:overcloud:undeploy_confirmation' stack_id %}{% endblock %} +{% block form_action %}{% url 'horizon:infrastructure:overview:undeploy_confirmation' %}{% endblock %} {% block modal_id %}provision_modal{% endblock %} {% block modal-header %}{% trans "Provisioning Confirmation" %}{% endblock %} @@ -21,5 +21,5 @@ {% block modal-footer %} - {% trans "Cancel" %} + {% trans "Cancel" %} {% endblock %} diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_undeploy_in_progress.html b/tuskar_ui/infrastructure/overview/templates/overview/_undeploy_in_progress.html similarity index 92% rename from tuskar_ui/infrastructure/overcloud/templates/overcloud/_undeploy_in_progress.html rename to tuskar_ui/infrastructure/overview/templates/overview/_undeploy_in_progress.html index af98ebb1d..e0a6eba33 100644 --- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_undeploy_in_progress.html +++ b/tuskar_ui/infrastructure/overview/templates/overview/_undeploy_in_progress.html @@ -41,7 +41,7 @@ {% endfor %} {% endif %} - See full log + See full log diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/detail.html b/tuskar_ui/infrastructure/overview/templates/overview/index.html similarity index 65% rename from tuskar_ui/infrastructure/overcloud/templates/overcloud/detail.html rename to tuskar_ui/infrastructure/overview/templates/overview/index.html index 086fa7f51..12da7fec9 100644 --- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/detail.html +++ b/tuskar_ui/infrastructure/overview/templates/overview/index.html @@ -19,14 +19,22 @@
- {{ tab_group.render }} + {% if stack %} + {% if stack.is_deleting or stack.is_delete_failed %} + {% include "infrastructure/overview/_undeploy_in_progress.html" %} + {% else %} + {% include "infrastructure/overview/_plan_overview.html" %} + {% endif %} + {% else %} + {% include "infrastructure/overview/_role_nodes.html" %} + {% endif %}
{% endblock %} diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/undeploy_confirmation.html b/tuskar_ui/infrastructure/overview/templates/overview/undeploy_confirmation.html similarity index 80% rename from tuskar_ui/infrastructure/overcloud/templates/overcloud/undeploy_confirmation.html rename to tuskar_ui/infrastructure/overview/templates/overview/undeploy_confirmation.html index 3bd1e0430..b234ac95b 100644 --- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/undeploy_confirmation.html +++ b/tuskar_ui/infrastructure/overview/templates/overview/undeploy_confirmation.html @@ -7,5 +7,5 @@ {% endblock page_header %} {% block infrastructure_main %} - {% include "infrastructure/overcloud/_undeploy_confirmation.html" %} + {% include "infrastructure/overview/_undeploy_confirmation.html" %} {% endblock %} diff --git a/tuskar_ui/infrastructure/overcloud/tests.py b/tuskar_ui/infrastructure/overview/tests.py similarity index 50% rename from tuskar_ui/infrastructure/overcloud/tests.py rename to tuskar_ui/infrastructure/overview/tests.py index e0356caca..ebb838b82 100644 --- a/tuskar_ui/infrastructure/overcloud/tests.py +++ b/tuskar_ui/infrastructure/overview/tests.py @@ -20,29 +20,15 @@ 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 flavor_data 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:overcloud:index') -DETAIL_URL = urlresolvers.reverse( - 'horizon:infrastructure:overcloud:detail', args=('stack-id-1',)) -UNDEPLOY_IN_PROGRESS_URL = urlresolvers.reverse( - 'horizon:infrastructure:overcloud:undeploy_in_progress', - args=('overcloud',)) -DETAIL_URL_CONFIGURATION_TAB = (DETAIL_URL + - "?tab=detail__configuration") + 'horizon:infrastructure:overview:index') DELETE_URL = urlresolvers.reverse( - 'horizon:infrastructure:overcloud:undeploy_confirmation', - args=('stack-id-1',)) -PLAN_CREATE_URL = urlresolvers.reverse( - 'horizon:infrastructure:plans:create') + 'horizon:infrastructure:overview:undeploy_confirmation') TEST_DATA = utils.TestDataContainer() -flavor_data.data(TEST_DATA) -node_data.data(TEST_DATA) heat_data.data(TEST_DATA) tuskar_data.data(TEST_DATA) @@ -79,80 +65,42 @@ def _mock_plan(**kwargs): class OvercloudTests(test.BaseAdminViewTests): - def test_index_overcloud_undeployed_get(self): - with _mock_plan(**{'get_the_plan.side_effect': None, - 'get_the_plan.return_value': None}): - res = self.client.get(INDEX_URL) - - self.assertRedirectsNoFollow(res, PLAN_CREATE_URL) - - def test_index_overcloud_deployed_stack_not_created(self): + def test_index_stack_not_created(self): with contextlib.nested( _mock_plan(), - patch('tuskar_ui.api.heat.Stack.is_deployed', - return_value=False), + patch('tuskar_ui.api.heat.Stack.list', + return_value=[]), ): res = self.client.get(INDEX_URL) request = api.tuskar.OvercloudPlan.get_the_plan. \ call_args_list[0][0][0] self.assertListEqual( api.tuskar.OvercloudPlan.get_the_plan.call_args_list, - [call(request)]) - self.assertRedirectsNoFollow(res, DETAIL_URL) + [call(request), call(request)]) + self.assertTemplateUsed( + res, 'infrastructure/overview/index.html') + self.assertTemplateUsed( + res, 'infrastructure/overview/_role_nodes.html') - def test_index_overcloud_deployed(self): - with _mock_plan() as OvercloudPlan: + def test_index_stack_deployed(self): + with contextlib.nested( + _mock_plan(), + patch('tuskar_ui.api.heat.Stack.get', + return_value=None), + patch('tuskar_ui.api.heat.Stack.events', + return_value=[]), + ) as (OvercloudPlan, stack_get_mock, stack_events_mock): res = self.client.get(INDEX_URL) request = OvercloudPlan.get_the_plan.call_args_list[0][0][0] self.assertListEqual(OvercloudPlan.get_the_plan.call_args_list, - [call(request)]) - - self.assertRedirectsNoFollow(res, DETAIL_URL) - - def test_detail_get(self): - roles = [api.tuskar.OvercloudRole(role) - for role in TEST_DATA.tuskarclient_roles.list()] - - with contextlib.nested( - _mock_plan(), - patch('tuskar_ui.api.tuskar.OvercloudRole', **{ - 'spec_set': ['list'], - 'list.return_value': roles, - }), - patch('tuskar_ui.api.heat.Stack.events', - return_value=[]), - ): - res = self.client.get(DETAIL_URL) + [call(request), call(request)]) self.assertTemplateUsed( - res, 'infrastructure/overcloud/detail.html') - self.assertTemplateNotUsed( - res, 'horizon/common/_detail_table.html') + res, 'infrastructure/overview/index.html') self.assertTemplateUsed( - res, 'infrastructure/overcloud/_detail_overview.html') + res, 'infrastructure/overview/_plan_overview.html') - def test_detail_get_configuration_tab(self): - with _mock_plan(): - res = self.client.get(DETAIL_URL_CONFIGURATION_TAB) - - self.assertTemplateUsed( - res, 'infrastructure/overcloud/detail.html') - self.assertTemplateNotUsed( - res, 'infrastructure/overcloud/_detail_overview.html') - self.assertTemplateUsed( - res, 'horizon/common/_detail_table.html') - - def test_delete_get(self): - res = self.client.get(DELETE_URL) - self.assertTemplateUsed( - res, 'infrastructure/overcloud/undeploy_confirmation.html') - - def test_delete_post(self): - with _mock_plan(): - res = self.client.post(DELETE_URL) - self.assertRedirectsNoFollow(res, INDEX_URL) - - def test_undeploy_in_progress(self): + def test_index_stack_undeploy_in_progress(self): with contextlib.nested( _mock_plan(), patch('tuskar_ui.api.heat.Stack.is_deleting', @@ -162,24 +110,19 @@ class OvercloudTests(test.BaseAdminViewTests): patch('tuskar_ui.api.heat.Stack.events', return_value=[]), ): - res = self.client.get(UNDEPLOY_IN_PROGRESS_URL) + res = self.client.get(INDEX_URL) self.assertTemplateUsed( - res, 'infrastructure/overcloud/detail.html') + res, 'infrastructure/overview/index.html') self.assertTemplateUsed( - res, 'infrastructure/overcloud/_undeploy_in_progress.html') - self.assertTemplateNotUsed( - res, 'horizon/common/_detail_table.html') + res, 'infrastructure/overview/_undeploy_in_progress.html') - def test_undeploy_in_progress_finished(self): - with _mock_plan(**{'get_the_plan.side_effect': None, - 'get_the_plan.return_value': None}): - res = self.client.get(UNDEPLOY_IN_PROGRESS_URL) + def test_delete_get(self): + res = self.client.get(DELETE_URL) + self.assertTemplateUsed( + res, 'infrastructure/overview/undeploy_confirmation.html') - self.assertRedirectsNoFollow(res, INDEX_URL) - - def test_undeploy_in_progress_invalid(self): + def test_delete_post(self): with _mock_plan(): - res = self.client.get(UNDEPLOY_IN_PROGRESS_URL) - - self.assertRedirectsNoFollow(res, DETAIL_URL) + res = self.client.post(DELETE_URL) + self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/tuskar_ui/infrastructure/plans/urls.py b/tuskar_ui/infrastructure/overview/urls.py similarity index 77% rename from tuskar_ui/infrastructure/plans/urls.py rename to tuskar_ui/infrastructure/overview/urls.py index 7ffa5d271..b8df03ba9 100644 --- a/tuskar_ui/infrastructure/plans/urls.py +++ b/tuskar_ui/infrastructure/overview/urls.py @@ -14,13 +14,13 @@ from django.conf import urls -from tuskar_ui.infrastructure.plans import views +from tuskar_ui.infrastructure.overview import views urlpatterns = urls.patterns( '', urls.url(r'^$', views.IndexView.as_view(), name='index'), - urls.url(r'^create/$', views.CreateView.as_view(), name='create'), - urls.url(r'^(?P[^/]+)/scale$', views.Scale.as_view(), - name='scale'), + urls.url(r'^undeploy-confirmation$', + views.UndeployConfirmationView.as_view(), + name='undeploy_confirmation'), ) diff --git a/tuskar_ui/infrastructure/overview/views.py b/tuskar_ui/infrastructure/overview/views.py new file mode 100644 index 000000000..489f5fcd7 --- /dev/null +++ b/tuskar_ui/infrastructure/overview/views.py @@ -0,0 +1,157 @@ +# -*- 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 + +import heatclient + +import horizon.forms +from horizon.utils import memoized +from horizon import views as horizon_views + +from tuskar_ui import api +from tuskar_ui.infrastructure.overview import forms + + +INDEX_URL = 'horizon:infrastructure:overview:index' + + +def _get_role_data(stack, role): + """Gathers data about a single deployment role from the related Overcloud + and OvercloudRole objects, and presents it in the form convenient for use + from the template. + + :param stack: Stack object + :type stack: tuskar_ui.api.heat.Stack + :param role: Role object + :type role: tuskar_ui.api.tuskar.OvercloudRole + :return: dict with information about the role, to be used by template + :rtype: dict + """ + resources = stack.resources_by_role(role, with_joins=True) + nodes = [r.node for r in resources] + node_count = len(nodes) + + data = { + 'role': role, + 'name': role.name, + 'total_node_count': node_count, + } + deployed_node_count = 0 + deploying_node_count = 0 + error_node_count = 0 + waiting_node_count = node_count + + if nodes: + deployed_node_count = sum(1 for node in nodes + if node.instance.status == 'ACTIVE') + deploying_node_count = sum(1 for node in nodes + if node.instance.status == 'BUILD') + error_node_count = sum(1 for node in nodes + if node.instance.status == 'ERROR') + waiting_node_count = (node_count - deployed_node_count - + deploying_node_count - error_node_count) + + data.update({ + 'deployed_node_count': deployed_node_count, + 'deploying_node_count': deploying_node_count, + 'waiting_node_count': waiting_node_count, + 'error_node_count': error_node_count, + }) + # TODO(rdopieralski) get this from ceilometer + # data['capacity'] = 20 + return data + + +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_views.APIView, StackMixin): + template_name = 'infrastructure/overview/index.html' + + def get_data(self, request, context, *args, **kwargs): + plan = api.tuskar.OvercloudPlan.get_the_plan(request) + stack = self.get_stack() + + context['plan'] = plan + context['stack'] = stack + + if stack: + context['last_failed_events'] = [ + e for e in stack.events + if e.resource_status == 'DELETE_FAILED'][-3:] + + if stack.is_deleting or stack.is_delete_failed: + # stack is being deleted + + # TODO(lsmola) since at this point we don't have total number + # of nodes we will hack this around, till API can show this + # information. So it will actually show progress like the total + # number is 10, or it will show progress of 5%. Ugly, but + # workable. + total_num_nodes_count = 10 + + try: + resources_count = len( + stack.resources(with_joins=False)) + except heatclient.exc.HTTPNotFound: + # Immediately after undeploying has started, heat returns + # this exception so we can take it as kind of init of + # undeploying. + resources_count = total_num_nodes_count + + # TODO(lsmola) same as hack above + total_num_nodes_count = max( + resources_count, total_num_nodes_count) + + context['progress'] = max( + 5, 100 * (total_num_nodes_count - resources_count)) + else: + # stack is active + roles = [_get_role_data(stack, role) + for role in stack.plan.role_list] + total = sum(d['total_node_count'] for d in roles) + + context['roles'] = roles + context['progress'] = 100 * sum(d.get('deployed_node_count', 0) + for d in roles) // (total or 1) + context['dashboard_urls'] = stack.dashboard_urls + + return context + + +class UndeployConfirmationView(horizon.forms.ModalFormView, StackMixin): + form_class = forms.UndeployOvercloud + template_name = 'infrastructure/overview/undeploy_confirmation.html' + + def get_success_url(self): + return reverse(INDEX_URL) + + def get_context_data(self, **kwargs): + context = super(UndeployConfirmationView, + self).get_context_data(**kwargs) + context['stack_id'] = self.get_stack().id + return context + + def get_initial(self, **kwargs): + initial = super(UndeployConfirmationView, self).get_initial(**kwargs) + initial['stack_id'] = self.get_stack().id + return initial diff --git a/tuskar_ui/infrastructure/plans/__init__.py b/tuskar_ui/infrastructure/plans/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/infrastructure/plans/forms.py b/tuskar_ui/infrastructure/plans/forms.py deleted file mode 100644 index 5d04b348f..000000000 --- a/tuskar_ui/infrastructure/plans/forms.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- 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 django.forms -from django.utils.translation import ugettext_lazy as _ -import horizon.exceptions -import horizon.forms -import horizon.messages -from openstack_dashboard import api as horizon_api - -from tuskar_ui import api - - -def get_flavor_choices(request): - empty = [('', '----')] - try: - flavors = horizon_api.nova.flavor_list(request, None) - except Exception: - horizon.exceptions.handle(request, - _('Unable to retrieve flavor list.')) - return empty - return empty + [(flavor.id, flavor.name) for flavor in flavors] - - -class OvercloudRoleForm(horizon.forms.SelfHandlingForm): - id = django.forms.IntegerField( - widget=django.forms.HiddenInput) - name = django.forms.CharField( - label=_("Name"), required=False, - widget=django.forms.TextInput( - attrs={'readonly': 'readonly', 'disabled': 'disabled'})) - description = django.forms.CharField( - label=_("Description"), required=False, - widget=django.forms.Textarea( - attrs={'readonly': 'readonly', 'disabled': 'disabled'})) - image_name = django.forms.CharField( - label=_("Image"), required=False, - widget=django.forms.TextInput( - attrs={'readonly': 'readonly', 'disabled': 'disabled'})) - flavor_id = django.forms.ChoiceField( - label=_("Flavor"), required=False, choices=()) - - def __init__(self, *args, **kwargs): - super(OvercloudRoleForm, self).__init__(*args, **kwargs) - self.fields['flavor_id'].choices = get_flavor_choices(self.request) - - def handle(self, request, context): - try: - role = api.tuskar.OvercloudRole.get(request, context['id']) - role.update(request, flavor_id=context['flavor_id']) - except Exception: - horizon.exceptions.handle(request, - _('Unable to update the role.')) - return False - return True diff --git a/tuskar_ui/infrastructure/plans/tables.py b/tuskar_ui/infrastructure/plans/tables.py deleted file mode 100644 index 2be5d6f91..000000000 --- a/tuskar_ui/infrastructure/plans/tables.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- 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 - - -class ConfigurationTable(tables.DataTable): - - key = tables.Column(lambda parameter: parameter[0], - verbose_name=_("Attribute Name")) - value = tables.Column(lambda parameter: parameter[1], - verbose_name=_("Attribute Value")) - - class Meta: - name = "configuration" - verbose_name = _("Configuration") - multi_select = False - table_actions = () - row_actions = () - - def get_object_id(self, datum): - return datum[0] diff --git a/tuskar_ui/infrastructure/plans/templates/plans/create_configuration.html b/tuskar_ui/infrastructure/plans/templates/plans/create_configuration.html deleted file mode 100644 index 93cbe0a92..000000000 --- a/tuskar_ui/infrastructure/plans/templates/plans/create_configuration.html +++ /dev/null @@ -1,9 +0,0 @@ -{% load i18n %} - -

{{ step.get_help_text }}

- -
-
- {% include 'horizon/common/_form_fields.html' with form=form %} -
-
diff --git a/tuskar_ui/infrastructure/plans/templates/plans/create_overview.html b/tuskar_ui/infrastructure/plans/templates/plans/create_overview.html deleted file mode 100644 index cdc9d75d4..000000000 --- a/tuskar_ui/infrastructure/plans/templates/plans/create_overview.html +++ /dev/null @@ -1,16 +0,0 @@ -{% load i18n %} -{% load url from future%} - - -
{{ step.get_help_text }}
- -
-
-

{% trans "Roles" %}

-
- - {% include "horizon/common/_form_fields.html" %} - -
-
-
diff --git a/tuskar_ui/infrastructure/plans/templates/plans/scale_node_counts.html b/tuskar_ui/infrastructure/plans/templates/plans/scale_node_counts.html deleted file mode 100644 index 56031a103..000000000 --- a/tuskar_ui/infrastructure/plans/templates/plans/scale_node_counts.html +++ /dev/null @@ -1,5 +0,0 @@ -{% load i18n %} - - -{% include 'infrastructure/overcloud/node_counts.html' with form=form show_change=True %} - diff --git a/tuskar_ui/infrastructure/plans/tests.py b/tuskar_ui/infrastructure/plans/tests.py deleted file mode 100644 index 1c1e6a572..000000000 --- a/tuskar_ui/infrastructure/plans/tests.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- 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 flavor_data -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:plans:index') -CREATE_URL = urlresolvers.reverse( - 'horizon:infrastructure:plans:create') -OVERCLOUD_INDEX_URL = urlresolvers.reverse( - 'horizon:infrastructure:overcloud:index') - -TEST_DATA = utils.TestDataContainer() -flavor_data.data(TEST_DATA) -node_data.data(TEST_DATA) -heat_data.data(TEST_DATA) -tuskar_data.data(TEST_DATA) - - -class OvercloudTests(test.BaseAdminViewTests): - - def test_index_no_plan_get(self): - with contextlib.nested( - patch('tuskar_ui.api.tuskar.OvercloudPlan.get_the_plan', - return_value=None), - ): - res = self.client.get(INDEX_URL) - self.assertRedirectsNoFollow(res, CREATE_URL) - - def test_index_with_plan_get(self): - plan = api.tuskar.OvercloudPlan(TEST_DATA.tuskarclient_plans.first()) - with contextlib.nested( - patch('tuskar_ui.api.tuskar.OvercloudPlan.get_the_plan', - return_value=plan), - ): - res = self.client.get(INDEX_URL) - request = api.tuskar.OvercloudPlan.get_the_plan. \ - call_args_list[0][0][0] - self.assertListEqual( - api.tuskar.OvercloudPlan.get_the_plan.call_args_list, - [call(request)]) - self.assertRedirectsNoFollow(res, OVERCLOUD_INDEX_URL) diff --git a/tuskar_ui/infrastructure/plans/views.py b/tuskar_ui/infrastructure/plans/views.py deleted file mode 100644 index a8958d863..000000000 --- a/tuskar_ui/infrastructure/plans/views.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- 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 django.views.generic import base as base_views - -from horizon.utils import memoized -import horizon.workflows - -from tuskar_ui import api -from tuskar_ui.infrastructure.plans.workflows import create -from tuskar_ui.infrastructure.plans.workflows import scale - - -INDEX_URL = 'horizon:infrastructure:plans:index' -CREATE_URL = 'horizon:infrastructure:plans:create' -OVERCLOUD_INDEX_URL = 'horizon:infrastructure:overcloud:index' - - -class OvercloudPlanMixin(object): - @memoized.memoized - def get_plan(self, redirect=None): - if redirect is None: - redirect = reverse(INDEX_URL) - plan_id = self.kwargs['plan_id'] - plan = api.tuskar.OvercloudPlan.get(self.request, plan_id, - _error_redirect=redirect) - return plan - - -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 - - def get_redirect_url(self): - plan = api.tuskar.OvercloudPlan.get_the_plan(self.request) - - if plan is None: - redirect = reverse(CREATE_URL) - else: - redirect = reverse(OVERCLOUD_INDEX_URL) - - return redirect - - -class CreateView(horizon.workflows.WorkflowView): - workflow_class = create.Workflow - template_name = 'infrastructure/_fullscreen_workflow_base.html' - - -class Scale(horizon.workflows.WorkflowView, OvercloudPlanMixin): - workflow_class = scale.Workflow - - def get_context_data(self, **kwargs): - context = super(Scale, self).get_context_data(**kwargs) - context['plan_id'] = self.kwargs['plan_id'] - return context - - def get_initial(self): - plan = self.get_plan() - overcloud_roles = dict((overcloud_role.id, overcloud_role) - for overcloud_role in - api.tuskar.OvercloudRole.list(self.request)) - - role_counts = dict(( - (count['overcloud_role_id'], - overcloud_roles[count['overcloud_role_id']].flavor_id), - count['num_nodes'], - ) for count in plan.counts) - return { - 'plan_id': plan.id, - 'role_counts': role_counts, - } diff --git a/tuskar_ui/infrastructure/plans/workflows/__init__.py b/tuskar_ui/infrastructure/plans/workflows/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/infrastructure/plans/workflows/create.py b/tuskar_ui/infrastructure/plans/workflows/create.py deleted file mode 100644 index 45cbae8a7..000000000 --- a/tuskar_ui/infrastructure/plans/workflows/create.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- 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 logging - -import django.forms -from django.utils.translation import ugettext_lazy as _ -import horizon.workflows - -from tuskar_ui import api -from tuskar_ui.infrastructure.plans.workflows import create_configuration -from tuskar_ui.infrastructure.plans.workflows import create_overview - - -LOG = logging.getLogger(__name__) - - -class Workflow(horizon.workflows.Workflow): - slug = 'create_plan' - name = _("My OpenStack Deployment Plan") - default_steps = ( - create_overview.Step, - create_configuration.Step, - ) - finalize_button_name = _("Deploy") - success_message = _("OpenStack deployment launched") - success_url = 'horizon:infrastructure:overcloud:index' - - def handle(self, request, context): - try: - api.tuskar.OvercloudPlan.create( - self.request, 'overcloud', 'overcloud') - except Exception as e: - # Showing error in both workflow tabs, because from the exception - # type we can't recognize where it should show - msg = unicode(e) - self.add_error_to_step(msg, 'create_overview') - self.add_error_to_step(msg, 'create_configuration') - LOG.exception('Error creating overcloud plan') - raise django.forms.ValidationError(msg) - return True diff --git a/tuskar_ui/infrastructure/plans/workflows/create_configuration.py b/tuskar_ui/infrastructure/plans/workflows/create_configuration.py deleted file mode 100644 index 13fd21207..000000000 --- a/tuskar_ui/infrastructure/plans/workflows/create_configuration.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- 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 django.forms -from django.utils.translation import ugettext_lazy as _ -import horizon.workflows - -from openstack_dashboard.api import neutron -from tuskar_ui.utils import utils - - -def make_field(name, Type, NoEcho, Default, Description, AllowedValues=None, - **kwargs): - """Create a form field using the parameters from a Heat template.""" - - label = utils.de_camel_case(name) - Widget = django.forms.TextInput - attrs = {} - widget_kwargs = {} - if Default == 'unset': - attrs['placeholder'] = _("auto-generate") - if Type == 'Json': - # TODO(lsmola) this should eventually be a textarea - Field = django.forms.CharField - else: - # TODO(lsmola) we should use Horizon code for generating of the form. - # There it should have list of all supported types according to Heat - # specification. - Field = django.forms.CharField - - if NoEcho == 'true': - Widget = django.forms.PasswordInput - widget_kwargs['render_value'] = True - if AllowedValues is not None: - return django.forms.ChoiceField(initial=Default, choices=[ - (value, value) for value in AllowedValues - ], help_text=Description, required=False, label=label) - return Field(widget=Widget(attrs=attrs, **widget_kwargs), initial=Default, - help_text=Description, required=False, label=label) - - -class Action(horizon.workflows.Action): - class Meta: - slug = 'deployed_configuration' - name = _("Configuration") - - def __init__(self, request, *args, **kwargs): - super(Action, self).__init__(request, *args, **kwargs) - params = [] - params.sort() - - for name, data in params: - # workaround for this parameter, which needs a preset taken from - # neutron - if name == 'NeutronControlPlaneID': - networks = neutron.network_list(request) - for network in networks: - if network.name == 'ctlplane': - data['Default'] = network.id - break - - self.fields[name] = make_field(name, **data) - - def clean(self): - # this is a workaround for a single parameter - if 'GlanceLogFile' in self.cleaned_data: - if not self.cleaned_data['GlanceLogFile']: - self.cleaned_data['GlanceLogFile'] = u"''" - return self.cleaned_data - - -class Step(horizon.workflows.Step): - action_class = Action - contributes = ('configuration',) - template_name = 'infrastructure/plans/create_configuration.html' - - def contribute(self, data, context): - context['configuration'] = data - return context diff --git a/tuskar_ui/infrastructure/plans/workflows/create_overview.py b/tuskar_ui/infrastructure/plans/workflows/create_overview.py deleted file mode 100644 index 45284694d..000000000 --- a/tuskar_ui/infrastructure/plans/workflows/create_overview.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- 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 forms -import horizon.workflows - -from tuskar_ui import api - - -class Action(horizon.workflows.Action): - - role_ids = forms.MultipleChoiceField( - label=_("Roles"), - required=True, - widget=forms.CheckboxSelectMultiple(), - help_text=_("Select roles for this plan.")) - - class Meta: - slug = 'create_overview' - name = _("Overview") - - def __init__(self, *args, **kwargs): - super(Action, self).__init__(*args, **kwargs) - - role_ids_choices = [] - roles = api.tuskar.OvercloudRole.list(self.request) - for r in roles: - role_ids_choices.append((r.id, r.name)) - self.fields['role_ids'].choices = sorted( - role_ids_choices) - - -class Step(horizon.workflows.Step): - action_class = Action - contributes = ('role_ids',) - template_name = 'infrastructure/plans/create_overview.html' - help_text = _("Nothing deployed yet. Design your first deployment.") - - def contribute(self, data, context): - context = super(Step, self).contribute(data, context) - return context diff --git a/tuskar_ui/infrastructure/plans/workflows/scale.py b/tuskar_ui/infrastructure/plans/workflows/scale.py deleted file mode 100644 index efa0a20ae..000000000 --- a/tuskar_ui/infrastructure/plans/workflows/scale.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- 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 django.utils.translation import ugettext_lazy as _ -from horizon import exceptions -import horizon.workflows - -from tuskar_ui import api -from tuskar_ui.infrastructure.plans.workflows import scale_node_counts - - -class Workflow(horizon.workflows.Workflow): - slug = 'scale_overcloud' - name = _("Scale Deployment") - default_steps = ( - scale_node_counts.Step, - ) - finalize_button_name = _("Apply Changes") - - def handle(self, request, context): - plan_id = context['plan_id'] - try: - # TODO(lsmola) when updates are fixed in Heat, figure out whether - # we need to send also parameters, right now we send {} - api.tuskar.OvercloudPlan.update(request, plan_id, - context['role_counts'], {}) - except Exception: - exceptions.handle(request, _('Unable to update deployment.')) - return False - return True - - def get_success_url(self): - plan_id = self.context.get('plan_id', 1) - return reverse('horizon:infrastructure:overcloud:detail', - args=(plan_id,)) diff --git a/tuskar_ui/infrastructure/plans/workflows/scale_node_counts.py b/tuskar_ui/infrastructure/plans/workflows/scale_node_counts.py deleted file mode 100644 index c001d1ee3..000000000 --- a/tuskar_ui/infrastructure/plans/workflows/scale_node_counts.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- 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 tuskar_ui.infrastructure.plans.workflows import create_overview - - -class Action(create_overview.Action): - class Meta: - slug = 'scale_node_counts' - name = _("Node Counts") - - -class Step(create_overview.Step): - action_class = Action - contributes = ('role_counts', 'plan_id') - template_name = 'infrastructure/plans/scale_node_counts.html' - - def prepare_action_context(self, request, context): - for (role_id, flavor_id), count in context['role_counts'].items(): - name = 'count__%s__%s' % (role_id, flavor_id) - context[name] = count - return context