
No overcloudrc was created when deployed via the UI. This adds the ability to view the values and to download an appropriate rc file. Change-Id: I2ee7cc4b10559a3542a6d22b01179b764c857d6b
391 lines
15 KiB
Python
391 lines
15 KiB
Python
# -*- 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 json
|
|
import logging
|
|
import urlparse
|
|
|
|
from django.core.urlresolvers import reverse
|
|
from django.core.urlresolvers import reverse_lazy
|
|
from django import http
|
|
from django import shortcuts
|
|
import django.utils.text
|
|
from django.utils.translation import ugettext_lazy as _
|
|
import heatclient
|
|
import horizon.forms
|
|
from horizon import messages
|
|
|
|
from tuskar_ui import api
|
|
from tuskar_ui.infrastructure.overview import forms
|
|
from tuskar_ui.infrastructure import views
|
|
|
|
|
|
INDEX_URL = 'horizon:infrastructure:overview:index'
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def _steps_message(messages):
|
|
total_steps = len(messages)
|
|
completed_steps = len([m for m in messages if not m.get('is_critical')])
|
|
return _("{0} of {1} Steps Completed").format(completed_steps, total_steps)
|
|
|
|
|
|
def _get_role_data(plan, stack, form, role):
|
|
"""Gathers data about a single deployment role.
|
|
|
|
Gathers data about a single deployment role from the related Overcloud
|
|
and Role objects, and presents it in the form convenient for use
|
|
from the template.
|
|
|
|
"""
|
|
data = {
|
|
'id': role.id,
|
|
'role': role,
|
|
'name': role.name,
|
|
'planned_node_count': plan.get_role_node_count(role),
|
|
'field': form['%s-count' % role.id] if form else '',
|
|
}
|
|
|
|
if stack:
|
|
resources = stack.resources(role=role, with_joins=True)
|
|
nodes = [r.node for r in resources]
|
|
node_count = len(nodes)
|
|
|
|
deployed_node_count = 0
|
|
deploying_node_count = 0
|
|
error_node_count = 0
|
|
waiting_node_count = node_count
|
|
|
|
status = 'warning'
|
|
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)
|
|
|
|
if error_node_count or 'FAILED' in stack.stack_status:
|
|
status = 'danger'
|
|
elif deployed_node_count == data['planned_node_count']:
|
|
status = 'success'
|
|
else:
|
|
status = 'info'
|
|
|
|
finished = deployed_node_count == data['planned_node_count']
|
|
if finished:
|
|
icon = 'fa-check'
|
|
elif status in ('danger', 'warning'):
|
|
icon = 'fa-exclamation'
|
|
else:
|
|
icon = 'fa-spinner fa-spin'
|
|
|
|
data.update({
|
|
'status': status,
|
|
'finished': finished,
|
|
'total_node_count': node_count,
|
|
'deployed_node_count': deployed_node_count,
|
|
'deploying_node_count': deploying_node_count,
|
|
'waiting_node_count': waiting_node_count,
|
|
'error_node_count': error_node_count,
|
|
'icon': icon,
|
|
})
|
|
|
|
# TODO(rdopieralski) get this from ceilometer
|
|
# data['capacity'] = 20
|
|
return data
|
|
|
|
|
|
class IndexView(horizon.forms.ModalFormView, views.StackMixin):
|
|
template_name = 'infrastructure/overview/index.html'
|
|
form_class = forms.EditPlan
|
|
success_url = reverse_lazy(INDEX_URL)
|
|
|
|
def get_progress_update(self, request, data):
|
|
return {
|
|
'progress': data.get('progress'),
|
|
'show_last_events': data.get('show_last_events'),
|
|
'last_events_title': unicode(data.get('last_events_title')),
|
|
'last_events': [{
|
|
'event_time': event.event_time,
|
|
'resource_name': event.resource_name,
|
|
'resource_status': event.resource_status,
|
|
'resource_status_reason': event.resource_status_reason,
|
|
} for event in data.get('last_events', [])],
|
|
'roles': [{
|
|
'status': role.get('status', 'warning'),
|
|
'finished': role.get('finished', False),
|
|
'name': role.get('name', ''),
|
|
'slug': django.utils.text.slugify(role.get('name', '')),
|
|
'id': role.get('id', ''),
|
|
'total_node_count': role.get('node_count', 0),
|
|
'deployed_node_count': role.get('deployed_node_count', 0),
|
|
'deploying_node_count': role.get('deploying_node_count', 0),
|
|
'waiting_node_count': role.get('waiting_node_count', 0),
|
|
'error_node_count': role.get('error_node_count', 0),
|
|
'planned_node_count': role.get('planned_node_count', 0),
|
|
'icon': role.get('icon', ''),
|
|
} for role in data.get('roles', [])],
|
|
}
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
if request.META.get('HTTP_X_HORIZON_PROGRESS', ''):
|
|
# If it's an AJAX call for progress update, send it.
|
|
data = self.get_data(request, {})
|
|
return http.HttpResponse(
|
|
json.dumps(self.get_progress_update(request, data)),
|
|
content_type='application/json',
|
|
)
|
|
return super(IndexView, self).get(request, *args, **kwargs)
|
|
|
|
def get_form(self, form_class):
|
|
return form_class(self.request, **self.get_form_kwargs())
|
|
|
|
def get_context_data(self, *args, **kwargs):
|
|
context = super(IndexView, self).get_context_data(*args, **kwargs)
|
|
context.update(self.get_data(self.request, context))
|
|
return context
|
|
|
|
def get_data(self, request, context, *args, **kwargs):
|
|
plan = api.tuskar.Plan.get_the_plan(request)
|
|
stack = self.get_stack()
|
|
form = context.get('form')
|
|
|
|
context['plan'] = plan
|
|
context['stack'] = stack
|
|
|
|
roles = [_get_role_data(plan, stack, form, role)
|
|
for role in plan.role_list]
|
|
context['roles'] = roles
|
|
|
|
if stack:
|
|
context['show_last_events'] = True
|
|
failed_events = [e for e in stack.events
|
|
if 'FAILED' in e.resource_status and
|
|
'aborted' not in e.resource_status_reason][-3:]
|
|
|
|
if failed_events:
|
|
context['last_events_title'] = _('Last failed events')
|
|
context['last_events'] = failed_events
|
|
else:
|
|
context['last_events_title'] = _('Last event')
|
|
context['last_events'] = [stack.events[0]]
|
|
|
|
if stack.is_deleting or stack.is_delete_failed:
|
|
# 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'] = min(95, max(
|
|
5, 100 * float(resources_count) / total_num_nodes_count))
|
|
elif stack.is_deploying or stack.is_updating:
|
|
total = sum(d['total_node_count'] for d in roles)
|
|
context['progress'] = min(95, max(
|
|
5, 100 * sum(float(d.get('deployed_node_count', 0))
|
|
for d in roles) / (total or 1)
|
|
))
|
|
else:
|
|
# stack is active
|
|
if not stack.is_failed:
|
|
context['show_last_events'] = False
|
|
context['progress'] = 100
|
|
controller_role = plan.get_role_by_name("Controller")
|
|
context['admin_password'] = plan.parameter_value(
|
|
controller_role.parameter_prefix + 'AdminPassword')
|
|
|
|
context['dashboard_urls'] = stack.dashboard_urls
|
|
no_proxy = [urlparse.urlparse(url).hostname
|
|
for url in stack.dashboard_urls]
|
|
context['no_proxy'] = ",".join(no_proxy)
|
|
context['auth_url'] = stack.keystone_auth_url
|
|
else:
|
|
messages = forms.validate_plan(request, plan)
|
|
context['plan_messages'] = messages
|
|
context['plan_invalid'] = any(message.get('is_critical')
|
|
for message in messages)
|
|
context['steps_message'] = _steps_message(messages)
|
|
return context
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
"""If the post comes from ajax, return validation results as json."""
|
|
|
|
if not request.META.get('HTTP_X_HORIZON_VALIDATE', ''):
|
|
return super(IndexView, self).post(request, *args, **kwargs)
|
|
form_class = self.get_form_class()
|
|
form = self.get_form(form_class)
|
|
if form.is_valid():
|
|
handled = form.handle(self.request, form.cleaned_data)
|
|
else:
|
|
handled = False
|
|
if handled:
|
|
messages = forms.validate_plan(request, form.plan)
|
|
else:
|
|
messages = [{
|
|
'text': _(u"Error saving the plan."),
|
|
'is_critical': True,
|
|
}]
|
|
messages.extend({
|
|
'text': repr(error),
|
|
} for error in form.non_field_errors)
|
|
messages.extend({
|
|
'text': repr(error),
|
|
} for field in form.fields for error in field.errors)
|
|
# We need to unlazify all the lazy urls and translations.
|
|
return http.HttpResponse(json.dumps({
|
|
'plan_invalid': any(m.get('is_critical') for m in messages),
|
|
'steps_message': _steps_message(messages),
|
|
'messages': [{
|
|
'text': unicode(m.get('text', '')),
|
|
'is_critical': m.get('is_critical', False),
|
|
'indent': m.get('indent', 0),
|
|
'classes': m['classes'],
|
|
} for m in messages],
|
|
}), content_type='application/json')
|
|
|
|
|
|
class DeployConfirmationView(horizon.forms.ModalFormView, views.StackMixin):
|
|
form_class = forms.DeployOvercloud
|
|
template_name = 'infrastructure/overview/deploy_confirmation.html'
|
|
submit_label = _("Deploy")
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super(DeployConfirmationView,
|
|
self).get_context_data(**kwargs)
|
|
plan = api.tuskar.Plan.get_the_plan(self.request)
|
|
|
|
context['autogenerated_parameters'] = (
|
|
plan.list_generated_parameters(with_prefix=False).keys())
|
|
return context
|
|
|
|
def get_success_url(self):
|
|
return reverse(INDEX_URL)
|
|
|
|
|
|
class UndeployConfirmationView(horizon.forms.ModalFormView, views.StackMixin):
|
|
form_class = forms.UndeployOvercloud
|
|
template_name = 'infrastructure/overview/undeploy_confirmation.html'
|
|
submit_label = _("Undeploy")
|
|
|
|
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
|
|
|
|
|
|
class PostDeployInitView(horizon.forms.ModalFormView, views.StackMixin):
|
|
form_class = forms.PostDeployInit
|
|
template_name = 'infrastructure/overview/post_deploy_init.html'
|
|
submit_label = _("Initialize")
|
|
|
|
def get_success_url(self):
|
|
return reverse(INDEX_URL)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super(PostDeployInitView,
|
|
self).get_context_data(**kwargs)
|
|
context['stack_id'] = self.get_stack().id
|
|
return context
|
|
|
|
def get_initial(self, **kwargs):
|
|
initial = super(PostDeployInitView, self).get_initial(**kwargs)
|
|
initial['stack_id'] = self.get_stack().id
|
|
initial['admin_email'] = getattr(self.request.user, 'email', '')
|
|
return initial
|
|
|
|
|
|
class ScaleOutView(horizon.forms.ModalFormView, views.StackMixin):
|
|
form_class = forms.ScaleOut
|
|
template_name = "infrastructure/overview/scale_out.html"
|
|
submit_label = _("Deploy Changes")
|
|
|
|
def get_success_url(self):
|
|
return reverse(INDEX_URL)
|
|
|
|
def get_form(self, form_class):
|
|
return form_class(self.request, **self.get_form_kwargs())
|
|
|
|
def get_context_data(self, *args, **kwargs):
|
|
context = super(ScaleOutView, self).get_context_data(*args, **kwargs)
|
|
plan = api.tuskar.Plan.get_the_plan(self.request)
|
|
form = context.get('form')
|
|
roles = [_get_role_data(plan, None, form, role)
|
|
for role in plan.role_list]
|
|
context.update({
|
|
'roles': roles,
|
|
'plan': plan,
|
|
})
|
|
return context
|
|
|
|
|
|
def _get_openrc_credentials(request):
|
|
plan = api.tuskar.Plan.get_the_plan(request)
|
|
stack = api.heat.Stack.get_by_plan(request, plan)
|
|
no_proxy = [urlparse.urlparse(url).hostname
|
|
for url in stack.dashboard_urls]
|
|
controller_role = plan.get_role_by_name("Controller")
|
|
credentials = dict(tenant_name='admin',
|
|
auth_url=stack.keystone_auth_url,
|
|
admin_password=plan.parameter_value(
|
|
controller_role.parameter_prefix + 'AdminPassword'),
|
|
no_proxy=",".join(no_proxy))
|
|
return credentials
|
|
|
|
|
|
def download_overcloudrc_file(request):
|
|
template = 'infrastructure/overview/overcloudrc.sh.template'
|
|
try:
|
|
context = _get_openrc_credentials(request)
|
|
|
|
response = shortcuts.render(request,
|
|
template,
|
|
context,
|
|
content_type="text/plain")
|
|
response['Content-Disposition'] = ('attachment; '
|
|
'filename="overcloudrc"')
|
|
response['Content-Length'] = str(len(response.content))
|
|
return response
|
|
|
|
except Exception as e:
|
|
LOG.exception("Exception in DownloadOvercloudrcForm.")
|
|
messages.error(request, _('Error Downloading RC File: %s') % e)
|
|
return shortcuts.redirect(request.build_absolute_uri())
|