3
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
# not use this file except in compliance with the License. You may obtain
5
# a copy of the License at
7
# http://www.apache.org/licenses/LICENSE-2.0
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
# License for the specific language governing permissions and limitations
19
from django.core.urlresolvers import reverse
20
from django.core.urlresolvers import reverse_lazy
21
from django import http
22
from django import shortcuts
23
import django.utils.text
24
from django.utils.translation import ugettext_lazy as _
27
from horizon import messages
29
from tuskar_ui import api
30
from tuskar_ui.infrastructure.overview import forms
31
from tuskar_ui.infrastructure import views
34
INDEX_URL = 'horizon:infrastructure:overview:index'
36
LOG = logging.getLogger(__name__)
39
def _steps_message(messages):
40
total_steps = len(messages)
41
completed_steps = len([m for m in messages if not m.get('is_critical')])
42
return _("{0} of {1} Steps Completed").format(completed_steps, total_steps)
45
def _get_role_data(plan, stack, form, role):
46
"""Gathers data about a single deployment role.
48
Gathers data about a single deployment role from the related Overcloud
49
and Role objects, and presents it in the form convenient for use
57
'planned_node_count': plan.get_role_node_count(role),
58
'field': form['%s-count' % role.id] if form else '',
62
resources = stack.resources(role=role, with_joins=True)
63
nodes = [r.node for r in resources]
64
node_count = len(nodes)
66
deployed_node_count = 0
67
deploying_node_count = 0
69
waiting_node_count = node_count
73
deployed_node_count = sum(1 for node in nodes
74
if node.instance.status == 'ACTIVE')
75
deploying_node_count = sum(1 for node in nodes
76
if node.instance.status == 'BUILD')
77
error_node_count = sum(1 for node in nodes
78
if node.instance.status == 'ERROR')
79
waiting_node_count = (node_count - deployed_node_count -
80
deploying_node_count - error_node_count)
82
if error_node_count or 'FAILED' in stack.stack_status:
84
elif deployed_node_count == data['planned_node_count']:
89
finished = deployed_node_count == data['planned_node_count']
92
elif status in ('danger', 'warning'):
93
icon = 'fa-exclamation'
95
icon = 'fa-spinner fa-spin'
100
'total_node_count': node_count,
101
'deployed_node_count': deployed_node_count,
102
'deploying_node_count': deploying_node_count,
103
'waiting_node_count': waiting_node_count,
104
'error_node_count': error_node_count,
108
# TODO(rdopieralski) get this from ceilometer
109
# data['capacity'] = 20
113
class IndexView(horizon.forms.ModalFormView, views.StackMixin):
114
template_name = 'infrastructure/overview/index.html'
115
form_class = forms.EditPlan
116
success_url = reverse_lazy(INDEX_URL)
118
def get_progress_update(self, request, data):
120
'progress': data.get('progress'),
121
'show_last_events': data.get('show_last_events'),
122
'last_events_title': unicode(data.get('last_events_title')),
124
'event_time': event.event_time,
125
'resource_name': event.resource_name,
126
'resource_status': event.resource_status,
127
'resource_status_reason': event.resource_status_reason,
128
} for event in data.get('last_events', [])],
130
'status': role.get('status', 'warning'),
131
'finished': role.get('finished', False),
132
'name': role.get('name', ''),
133
'slug': django.utils.text.slugify(role.get('name', '')),
134
'id': role.get('id', ''),
135
'total_node_count': role.get('node_count', 0),
136
'deployed_node_count': role.get('deployed_node_count', 0),
137
'deploying_node_count': role.get('deploying_node_count', 0),
138
'waiting_node_count': role.get('waiting_node_count', 0),
139
'error_node_count': role.get('error_node_count', 0),
140
'planned_node_count': role.get('planned_node_count', 0),
141
'icon': role.get('icon', ''),
142
} for role in data.get('roles', [])],
145
def get(self, request, *args, **kwargs):
146
if request.META.get('HTTP_X_HORIZON_PROGRESS', ''):
147
# If it's an AJAX call for progress update, send it.
148
data = self.get_data(request, {})
149
return http.HttpResponse(
150
json.dumps(self.get_progress_update(request, data)),
151
content_type='application/json',
153
return super(IndexView, self).get(request, *args, **kwargs)
155
def get_form(self, form_class):
156
return form_class(self.request, **self.get_form_kwargs())
158
def get_context_data(self, *args, **kwargs):
159
context = super(IndexView, self).get_context_data(*args, **kwargs)
160
context.update(self.get_data(self.request, context))
163
def get_data(self, request, context, *args, **kwargs):
164
plan = api.tuskar.Plan.get_the_plan(request)
165
stack = self.get_stack()
166
form = context.get('form')
168
context['plan'] = plan
169
context['stack'] = stack
171
roles = [_get_role_data(plan, stack, form, role)
172
for role in plan.role_list]
173
context['roles'] = roles
176
context['show_last_events'] = True
177
failed_events = [e for e in stack.events
178
if 'FAILED' in e.resource_status and
179
'aborted' not in e.resource_status_reason][-3:]
182
context['last_events_title'] = _('Last failed events')
183
context['last_events'] = failed_events
185
context['last_events_title'] = _('Last event')
186
context['last_events'] = [stack.events[0]]
188
if stack.is_deleting or stack.is_delete_failed:
189
# TODO(lsmola) since at this point we don't have total number
190
# of nodes we will hack this around, till API can show this
191
# information. So it will actually show progress like the total
192
# number is 10, or it will show progress of 5%. Ugly, but
194
total_num_nodes_count = 10
197
resources_count = len(
198
stack.resources(with_joins=False))
199
except heatclient.exc.HTTPNotFound:
200
# Immediately after undeploying has started, heat returns
201
# this exception so we can take it as kind of init of
203
resources_count = total_num_nodes_count
205
# TODO(lsmola) same as hack above
206
total_num_nodes_count = max(
207
resources_count, total_num_nodes_count)
209
context['progress'] = min(95, max(
210
5, 100 * float(resources_count) / total_num_nodes_count))
211
elif stack.is_deploying or stack.is_updating:
212
total = sum(d['total_node_count'] for d in roles)
213
context['progress'] = min(95, max(
214
5, 100 * sum(float(d.get('deployed_node_count', 0))
215
for d in roles) / (total or 1)
219
if not stack.is_failed:
220
context['show_last_events'] = False
221
context['progress'] = 100
222
controller_role = plan.get_role_by_name("Controller")
223
context['admin_password'] = plan.parameter_value(
224
controller_role.parameter_prefix + 'AdminPassword')
226
context['dashboard_urls'] = stack.dashboard_urls
227
no_proxy = [urlparse.urlparse(url).hostname
228
for url in stack.dashboard_urls]
229
context['no_proxy'] = ",".join(no_proxy)
230
context['auth_url'] = stack.keystone_auth_url
232
messages = forms.validate_plan(request, plan)
233
context['plan_messages'] = messages
234
context['plan_invalid'] = any(message.get('is_critical')
235
for message in messages)
236
context['steps_message'] = _steps_message(messages)
239
def post(self, request, *args, **kwargs):
240
"""If the post comes from ajax, return validation results as json."""
242
if not request.META.get('HTTP_X_HORIZON_VALIDATE', ''):
243
return super(IndexView, self).post(request, *args, **kwargs)
244
form_class = self.get_form_class()
245
form = self.get_form(form_class)
247
handled = form.handle(self.request, form.cleaned_data)
251
messages = forms.validate_plan(request, form.plan)
254
'text': _(u"Error saving the plan."),
259
} for error in form.non_field_errors)
262
} for field in form.fields for error in field.errors)
263
# We need to unlazify all the lazy urls and translations.
264
return http.HttpResponse(json.dumps({
265
'plan_invalid': any(m.get('is_critical') for m in messages),
266
'steps_message': _steps_message(messages),
268
'text': unicode(m.get('text', '')),
269
'is_critical': m.get('is_critical', False),
270
'indent': m.get('indent', 0),
271
'classes': m['classes'],
272
} for m in messages],
273
}), content_type='application/json')
276
class DeployConfirmationView(horizon.forms.ModalFormView, views.StackMixin):
277
form_class = forms.DeployOvercloud
278
template_name = 'infrastructure/overview/deploy_confirmation.html'
279
submit_label = _("Deploy")
281
def get_context_data(self, **kwargs):
282
context = super(DeployConfirmationView,
283
self).get_context_data(**kwargs)
284
plan = api.tuskar.Plan.get_the_plan(self.request)
286
context['autogenerated_parameters'] = (
287
plan.list_generated_parameters(with_prefix=False).keys())
290
def get_success_url(self):
291
return reverse(INDEX_URL)
294
class UndeployConfirmationView(horizon.forms.ModalFormView, views.StackMixin):
295
form_class = forms.UndeployOvercloud
296
template_name = 'infrastructure/overview/undeploy_confirmation.html'
297
submit_label = _("Undeploy")
299
def get_success_url(self):
300
return reverse(INDEX_URL)
302
def get_context_data(self, **kwargs):
303
context = super(UndeployConfirmationView,
304
self).get_context_data(**kwargs)
305
context['stack_id'] = self.get_stack().id
308
def get_initial(self, **kwargs):
309
initial = super(UndeployConfirmationView, self).get_initial(**kwargs)
310
initial['stack_id'] = self.get_stack().id
314
class PostDeployInitView(horizon.forms.ModalFormView, views.StackMixin):
315
form_class = forms.PostDeployInit
316
template_name = 'infrastructure/overview/post_deploy_init.html'
317
submit_label = _("Initialize")
319
def get_success_url(self):
320
return reverse(INDEX_URL)
322
def get_context_data(self, **kwargs):
323
context = super(PostDeployInitView,
324
self).get_context_data(**kwargs)
325
context['stack_id'] = self.get_stack().id
328
def get_initial(self, **kwargs):
329
initial = super(PostDeployInitView, self).get_initial(**kwargs)
330
initial['stack_id'] = self.get_stack().id
331
initial['admin_email'] = getattr(self.request.user, 'email', '')
335
class ScaleOutView(horizon.forms.ModalFormView, views.StackMixin):
336
form_class = forms.ScaleOut
337
template_name = "infrastructure/overview/scale_out.html"
338
submit_label = _("Deploy Changes")
340
def get_success_url(self):
341
return reverse(INDEX_URL)
343
def get_form(self, form_class):
344
return form_class(self.request, **self.get_form_kwargs())
346
def get_context_data(self, *args, **kwargs):
347
context = super(ScaleOutView, self).get_context_data(*args, **kwargs)
348
plan = api.tuskar.Plan.get_the_plan(self.request)
349
form = context.get('form')
350
roles = [_get_role_data(plan, None, form, role)
351
for role in plan.role_list]
359
def _get_openrc_credentials(request):
360
plan = api.tuskar.Plan.get_the_plan(request)
361
stack = api.heat.Stack.get_by_plan(request, plan)
362
no_proxy = [urlparse.urlparse(url).hostname
363
for url in stack.dashboard_urls]
364
controller_role = plan.get_role_by_name("Controller")
365
credentials = dict(tenant_name='admin',
366
auth_url=stack.keystone_auth_url,
367
admin_password=plan.parameter_value(
368
controller_role.parameter_prefix + 'AdminPassword'),
369
no_proxy=",".join(no_proxy))
373
def download_overcloudrc_file(request):
374
template = 'infrastructure/overview/overcloudrc.sh.template'
376
context = _get_openrc_credentials(request)
378
response = shortcuts.render(request,
381
content_type="text/plain")
382
response['Content-Disposition'] = ('attachment; '
383
'filename="overcloudrc"')
384
response['Content-Length'] = str(len(response.content))
387
except Exception as e:
388
LOG.exception("Exception in DownloadOvercloudrcForm.")
389
messages.error(request, _('Error Downloading RC File: %s') % e)
390
return shortcuts.redirect(request.build_absolute_uri())