Merge "Undeploy in progress page"
This commit is contained in:
commit
36a0bc621b
@ -370,7 +370,7 @@ class Overcloud(base.APIResourceWrapper):
|
||||
|
||||
the_overcloud = cls(object(), request=request)
|
||||
# I need to mock attributes of overcloud that is being deleted.
|
||||
the_overcloud.id = "deleting_in_progress"
|
||||
the_overcloud.id = "overcloud"
|
||||
|
||||
if the_overcloud.stack and the_overcloud.is_deleting:
|
||||
return the_overcloud
|
||||
@ -398,12 +398,6 @@ class Overcloud(base.APIResourceWrapper):
|
||||
found
|
||||
:rtype: heatclient.v1.stacks.Stack or None
|
||||
"""
|
||||
# TODO(lsmola) load it properly, once the API has finished workflow
|
||||
# and for example there can't be a situation when I delete Overcloud
|
||||
# but Stack is still deleting. So the Overcloud will represent the
|
||||
# state of all inner entities and operations correctly.
|
||||
# Then also delete the try/except, it should not be caught on this
|
||||
# level.
|
||||
return heat.stack_get(self._request, 'overcloud')
|
||||
|
||||
@cached_property
|
||||
@ -449,7 +443,7 @@ class Overcloud(base.APIResourceWrapper):
|
||||
:rtype: bool
|
||||
"""
|
||||
return self.stack.stack_status in ('CREATE_FAILED',
|
||||
'UPDATE_FAILED')
|
||||
'UPDATE_FAILED',)
|
||||
|
||||
@cached_property
|
||||
def is_deleting(self):
|
||||
@ -460,6 +454,15 @@ class Overcloud(base.APIResourceWrapper):
|
||||
"""
|
||||
return self.stack.stack_status in ('DELETE_IN_PROGRESS', )
|
||||
|
||||
@cached_property
|
||||
def is_delete_failed(self):
|
||||
"""Check if this Overcloud deleting has failed.
|
||||
|
||||
:return: True if Overcloud deleting has failed, False otherwise.
|
||||
:rtype: bool
|
||||
"""
|
||||
return self.stack.stack_status in ('DELETE_FAILED', )
|
||||
|
||||
@memoized.memoized
|
||||
def all_resources(self, with_joins=True):
|
||||
"""Return a list of all Overcloud Resources
|
||||
@ -472,16 +475,9 @@ class Overcloud(base.APIResourceWrapper):
|
||||
are none
|
||||
:rtype: list of tuskar_ui.api.Resource
|
||||
"""
|
||||
# FIXME(lsmola) of this is a temporary hack. When I delete the stack
|
||||
# there is a brief moment when list of resources throws an exception
|
||||
# a second later, it does not. So the delete in progress page will
|
||||
# need to be separated, because it is 'special'. Till then, this hack
|
||||
# stays.
|
||||
try:
|
||||
resources = [r for r in heat.resources_list(self._request,
|
||||
self.stack.stack_name)]
|
||||
except heatclient.exc.HTTPNotFound:
|
||||
resources = []
|
||||
except heatclient.exc.HTTPInternalServerError:
|
||||
# TODO(lsmola) There is a weird bug in heat, that after
|
||||
# stack-create it returns 500 for a little while. This can be
|
||||
|
@ -13,6 +13,7 @@
|
||||
# under the License.
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import heatclient
|
||||
from horizon import tabs
|
||||
|
||||
from tuskar_ui import api
|
||||
@ -100,6 +101,45 @@ class OverviewTab(tabs.Tab):
|
||||
}
|
||||
|
||||
|
||||
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):
|
||||
overcloud = self.tab_group.kwargs['overcloud']
|
||||
|
||||
# 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:
|
||||
all_resources_count = len(
|
||||
overcloud.all_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.
|
||||
all_resources_count = total_num_nodes_count
|
||||
|
||||
# TODO(lsmola) same as hack above
|
||||
total_num_nodes_count = max(all_resources_count, total_num_nodes_count)
|
||||
|
||||
delete_progress = max(
|
||||
5, 100 * (total_num_nodes_count - all_resources_count))
|
||||
|
||||
events = overcloud.stack_events
|
||||
last_failed_events = [e for e in events
|
||||
if e.resource_status == 'DELETE_FAILED'][-3:]
|
||||
return {
|
||||
'overcloud': overcloud,
|
||||
'progress': delete_progress,
|
||||
'last_failed_events': last_failed_events,
|
||||
}
|
||||
|
||||
|
||||
class ConfigurationTab(tabs.TableTab):
|
||||
table_classes = (tables.ConfigurationTable,)
|
||||
name = _("Configuration")
|
||||
@ -126,6 +166,12 @@ class LogTab(tabs.TableTab):
|
||||
return overcloud.stack_events
|
||||
|
||||
|
||||
class UndeployInProgressTabs(tabs.TabGroup):
|
||||
slug = "undeploy_in_progress"
|
||||
tabs = (UndeployInProgressTab, LogTab)
|
||||
sticky = True
|
||||
|
||||
|
||||
class DetailTabs(tabs.TabGroup):
|
||||
slug = "detail"
|
||||
tabs = (OverviewTab, ConfigurationTab, LogTab)
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% load i18n %}
|
||||
{% load url from future%}
|
||||
|
||||
{% if overcloud.is_deploying or overcloud.is_failed or overcloud.is_deleting %}
|
||||
{% if overcloud.is_deploying or overcloud.is_failed %}
|
||||
{% if overcloud.is_deploying %}
|
||||
<div class="alert alert-info">
|
||||
<div class="row-fluid">
|
||||
@ -11,15 +11,6 @@
|
||||
<div class="bar bar-info" style="width:{{ progress }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% elif overcloud.is_deleting %}
|
||||
<div class="alert alert-error">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<strong>Undeploying...</strong>
|
||||
<div class="progress progress-striped progress-danger">
|
||||
<div class="bar bar-info" style="width:{{ progress }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-error">
|
||||
<div class="row-fluid">
|
||||
@ -54,7 +45,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not dashboard_urls and not overcloud.is_deploying and not overcloud.is_failed and not overcloud.is_deleting %}
|
||||
{% if not dashboard_urls and not overcloud.is_deploying and not overcloud.is_failed %}
|
||||
<div class="row-fluid">
|
||||
<div class="span8">
|
||||
<p>Your OpenStack Deployment has been successfully deployed. It needs to be initialized before you will be able to use it.
|
||||
|
@ -0,0 +1,57 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<div class="actions pull-right">
|
||||
</div>
|
||||
{% if overcloud.is_deleting %}
|
||||
<div class="alert alert-error">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<strong>Undeploying...</strong>
|
||||
<div class="progress progress-striped progress-danger">
|
||||
<div class="bar bar-info" style="width:{{ progress }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-error">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<strong>Undeploying failed</strong>
|
||||
<div class="progress progress-striped progress-danger">
|
||||
<div class="bar bar-info" style="width:{{ progress }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="span10">
|
||||
{% if last_failed_events %}
|
||||
<strong>{% trans "Last failed events:" %}</strong>
|
||||
{% for event in last_failed_events %}
|
||||
<div>
|
||||
<dl>
|
||||
<dt>{% trans "Timestamp" %}</dt>
|
||||
<dd><time datetime="{{ event.event_time }}">{{ event.event_time }}</time></dd>
|
||||
<dt>{% trans "Resource Name" %}</dt>
|
||||
<dd>{{ event.resource_name }}</dd>
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>{{ event.resource_status }}</dd>
|
||||
<dt>{% trans "Reason" %}</dt>
|
||||
<dd>{{ event.resource_status_reason }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<a href="?tab=undeploy_in_progress__log" data-toggle="tab" data-target="#undeploy_in_progress__log" class="pull-right">See full log</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(window.$ || window.addHorizonLoadEvent)(function () {
|
||||
$('div > a[data-target="#undeploy_in_progress__log"]').click(function () {
|
||||
$('li > a[data-target="#undeploy_in_progress__log"]').tab('show');
|
||||
});
|
||||
});
|
||||
</script>
|
@ -29,6 +29,11 @@ CREATE_URL = urlresolvers.reverse(
|
||||
'horizon:infrastructure:overcloud:create')
|
||||
DETAIL_URL = urlresolvers.reverse(
|
||||
'horizon:infrastructure:overcloud:detail', args=(1,))
|
||||
UNDEPLOY_IN_PROGRESS_URL = urlresolvers.reverse(
|
||||
'horizon:infrastructure:overcloud:undeploy_in_progress',
|
||||
args=('overcloud',))
|
||||
UNDEPLOY_IN_PROGRESS_URL_LOG_TAB = (
|
||||
UNDEPLOY_IN_PROGRESS_URL + "?tab=undeploy_in_progress__log")
|
||||
DETAIL_URL_CONFIGURATION_TAB = (DETAIL_URL +
|
||||
"?tab=detail__configuration")
|
||||
DETAIL_URL_LOG_TAB = (DETAIL_URL + "?tab=detail__log")
|
||||
@ -70,7 +75,10 @@ def _mock_overcloud(**kwargs):
|
||||
'id',
|
||||
'is_deployed',
|
||||
'is_deploying',
|
||||
'is_deleting',
|
||||
'is_delete_failed',
|
||||
'is_failed',
|
||||
'all_resources',
|
||||
'resources',
|
||||
'stack',
|
||||
'stack_events',
|
||||
@ -86,7 +94,10 @@ def _mock_overcloud(**kwargs):
|
||||
'id': 1,
|
||||
'is_deployed': True,
|
||||
'is_deploying': False,
|
||||
'is_deleting': False,
|
||||
'is_delete_failed': False,
|
||||
'is_failed': False,
|
||||
'all_resources.return_value': [],
|
||||
'resources.return_value': [],
|
||||
'stack_events': [],
|
||||
'stack': stack,
|
||||
@ -102,7 +113,8 @@ def _mock_overcloud(**kwargs):
|
||||
class OvercloudTests(test.BaseAdminViewTests):
|
||||
|
||||
def test_index_overcloud_undeployed_get(self):
|
||||
with patch('tuskar_ui.api.Overcloud.list', return_value=[]):
|
||||
with _mock_overcloud(**{'get_the_overcloud.side_effect': None,
|
||||
'get_the_overcloud.return_value': None}):
|
||||
res = self.client.get(INDEX_URL)
|
||||
|
||||
self.assertRedirectsNoFollow(res, CREATE_URL)
|
||||
@ -305,6 +317,41 @@ class OvercloudTests(test.BaseAdminViewTests):
|
||||
res = self.client.post(DELETE_URL)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
def test_undeploy_in_progress(self):
|
||||
with _mock_overcloud(is_deleting=True, is_deployed=False):
|
||||
res = self.client.get(UNDEPLOY_IN_PROGRESS_URL)
|
||||
|
||||
self.assertTemplateUsed(
|
||||
res, 'infrastructure/overcloud/detail.html')
|
||||
self.assertTemplateUsed(
|
||||
res, 'infrastructure/overcloud/_undeploy_in_progress.html')
|
||||
self.assertTemplateNotUsed(
|
||||
res, 'horizon/common/_detail_table.html')
|
||||
|
||||
def test_undeploy_in_progress_finished(self):
|
||||
with _mock_overcloud(**{'get_the_overcloud.side_effect': None,
|
||||
'get_the_overcloud.return_value': None}):
|
||||
res = self.client.get(UNDEPLOY_IN_PROGRESS_URL)
|
||||
|
||||
self.assertRedirectsNoFollow(res, CREATE_URL)
|
||||
|
||||
def test_undeploy_in_progress_invalid(self):
|
||||
with _mock_overcloud():
|
||||
res = self.client.get(UNDEPLOY_IN_PROGRESS_URL)
|
||||
|
||||
self.assertRedirectsNoFollow(res, DETAIL_URL)
|
||||
|
||||
def test_undeploy_in_progress_log_tab(self):
|
||||
with _mock_overcloud(is_deleting=True, is_deployed=False):
|
||||
res = self.client.get(UNDEPLOY_IN_PROGRESS_URL_LOG_TAB)
|
||||
|
||||
self.assertTemplateUsed(
|
||||
res, 'infrastructure/overcloud/detail.html')
|
||||
self.assertTemplateNotUsed(
|
||||
res, 'infrastructure/overcloud/_undeploy_in_progress.html')
|
||||
self.assertTemplateUsed(
|
||||
res, 'horizon/common/_detail_table.html')
|
||||
|
||||
def test_scale_get(self):
|
||||
oc = None
|
||||
roles = TEST_DATA.tuskarclient_overcloud_roles.list()
|
||||
|
@ -21,6 +21,9 @@ 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<overcloud_id>[^/]+)/undeploy-in-progress$',
|
||||
views.UndeployInProgressView.as_view(),
|
||||
name='undeploy_in_progress'),
|
||||
urls.url(r'^create/role-edit/(?P<role_id>[^/]+)$',
|
||||
views.OvercloudRoleEdit.as_view(), name='role_edit'),
|
||||
urls.url(r'^(?P<overcloud_id>[^/]+)/$', views.DetailView.as_view(),
|
||||
|
@ -17,7 +17,10 @@ from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import base as base_views
|
||||
|
||||
import heatclient
|
||||
from horizon import exceptions as horizon_exceptions
|
||||
import horizon.forms
|
||||
from horizon import messages
|
||||
from horizon import tables as horizon_tables
|
||||
from horizon import tabs as horizon_tabs
|
||||
from horizon.utils import memoized
|
||||
@ -33,6 +36,10 @@ from tuskar_ui.infrastructure.overcloud.workflows import undeployed
|
||||
|
||||
|
||||
INDEX_URL = 'horizon:infrastructure:overcloud:index'
|
||||
DETAIL_URL = 'horizon:infrastructure:overcloud:detail'
|
||||
CREATE_URL = 'horizon:infrastructure:overcloud:create'
|
||||
UNDEPLOY_IN_PROGRESS_URL = (
|
||||
'horizon:infrastructure:overcloud:undeploy_in_progress')
|
||||
|
||||
|
||||
class OvercloudMixin(object):
|
||||
@ -60,22 +67,21 @@ class IndexView(base_views.RedirectView):
|
||||
|
||||
def get_redirect_url(self):
|
||||
try:
|
||||
# TODO(lsmola) implement this properly when supported by API
|
||||
# TODO(lsmola) implement this properly when supported by API
|
||||
overcloud = api.Overcloud.get_the_overcloud(self.request)
|
||||
except Exception:
|
||||
except heatclient.exc.HTTPNotFound:
|
||||
overcloud = None
|
||||
|
||||
if overcloud is not None:
|
||||
# TODO(lsmola) there can be a short period when overcloud
|
||||
# is created, but stack not. So we have to make sure we have
|
||||
# missing stack under control as a new STATE
|
||||
# Also when deleting now, it first deletes Overcloud then Stack
|
||||
# because stack takes much longer to delete. But we can probably
|
||||
# ignore it for now and fix the worflow on API side.
|
||||
redirect = reverse('horizon:infrastructure:overcloud:detail',
|
||||
redirect = None
|
||||
if overcloud is None:
|
||||
redirect = reverse(CREATE_URL)
|
||||
elif overcloud.is_deleting or overcloud.is_delete_failed:
|
||||
redirect = reverse(UNDEPLOY_IN_PROGRESS_URL,
|
||||
args=(overcloud.id,))
|
||||
else:
|
||||
redirect = reverse('horizon:infrastructure:overcloud:create')
|
||||
redirect = reverse(DETAIL_URL,
|
||||
args=(overcloud.id,))
|
||||
|
||||
return redirect
|
||||
|
||||
|
||||
@ -103,7 +109,7 @@ class UndeployConfirmationView(horizon.forms.ModalFormView):
|
||||
template_name = 'infrastructure/overcloud/undeploy_confirmation.html'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('horizon:infrastructure:overcloud:index')
|
||||
return reverse(INDEX_URL)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UndeployConfirmationView,
|
||||
@ -117,6 +123,42 @@ class UndeployConfirmationView(horizon.forms.ModalFormView):
|
||||
return initial
|
||||
|
||||
|
||||
class UndeployInProgressView(horizon_tabs.TabView, OvercloudMixin, ):
|
||||
tab_group_class = tabs.UndeployInProgressTabs
|
||||
template_name = 'infrastructure/overcloud/detail.html'
|
||||
|
||||
def get_overcloud_or_redirect(self):
|
||||
try:
|
||||
# TODO(lsmola) implement this properly when supported by API
|
||||
overcloud = api.Overcloud.get_the_overcloud(self.request)
|
||||
except heatclient.exc.HTTPNotFound:
|
||||
overcloud = None
|
||||
|
||||
if overcloud is None:
|
||||
redirect = reverse(CREATE_URL)
|
||||
messages.success(self.request,
|
||||
_("Undeploying of the Overcloud has finished."))
|
||||
raise horizon_exceptions.Http302(redirect)
|
||||
elif overcloud.is_deleting or overcloud.is_delete_failed:
|
||||
return overcloud
|
||||
else:
|
||||
messages.error(self.request,
|
||||
_("Overcloud is not being undeployed."))
|
||||
redirect = reverse(DETAIL_URL,
|
||||
args=(overcloud.id,))
|
||||
raise horizon_exceptions.Http302(redirect)
|
||||
|
||||
def get_tabs(self, request, **kwargs):
|
||||
overcloud = self.get_overcloud_or_redirect()
|
||||
return self.tab_group_class(request, overcloud=overcloud, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UndeployInProgressView,
|
||||
self).get_context_data(**kwargs)
|
||||
context['overcloud'] = self.get_overcloud_or_redirect()
|
||||
return context
|
||||
|
||||
|
||||
class Scale(horizon.workflows.WorkflowView, OvercloudMixin):
|
||||
workflow_class = scale.Workflow
|
||||
|
||||
@ -154,7 +196,7 @@ class OvercloudRoleView(horizon_tables.DataTableView,
|
||||
|
||||
def get_data(self):
|
||||
overcloud = self.get_overcloud()
|
||||
redirect = reverse('horizon:infrastructure:overcloud:detail',
|
||||
redirect = reverse(DETAIL_URL,
|
||||
args=(overcloud.id,))
|
||||
role = self.get_role(redirect)
|
||||
return self._get_nodes(overcloud, role)
|
||||
@ -163,7 +205,7 @@ class OvercloudRoleView(horizon_tables.DataTableView,
|
||||
context = super(OvercloudRoleView, self).get_context_data(**kwargs)
|
||||
|
||||
overcloud = self.get_overcloud()
|
||||
redirect = reverse('horizon:infrastructure:overcloud:detail',
|
||||
redirect = reverse(DETAIL_URL,
|
||||
args=(overcloud.id,))
|
||||
role = self.get_role(redirect)
|
||||
context['role'] = role
|
||||
@ -185,7 +227,7 @@ class OvercloudRoleEdit(horizon.forms.ModalFormView, OvercloudRoleMixin):
|
||||
template_name = 'infrastructure/overcloud/role_edit.html'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('horizon:infrastructure:overcloud:create')
|
||||
return reverse(CREATE_URL)
|
||||
|
||||
def get_initial(self):
|
||||
role = self.get_role()
|
||||
|
Loading…
x
Reference in New Issue
Block a user