~tripleo/tuskar-ui/trunk

« back to all changes in this revision

Viewing changes to tuskar_ui/infrastructure/overview/views.py

  • Committer: Dougal Matthews
  • Date: 2016-01-22 11:26:13 UTC
  • Revision ID: git-v1:31e0bb84f6a412777a69781d1e6438b56a28c64b
Retire the Tuskar UI codebase

Change-Id: I469fdc1339d4991586bf2e1d62d99fd5b68289eb
Depends-On: I904b2f27591333e104bf9080bb8c3876fcb3596c

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf8 -*-
2
 
#
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
6
 
#
7
 
#         http://www.apache.org/licenses/LICENSE-2.0
8
 
#
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
13
 
#    under the License.
14
 
 
15
 
import json
16
 
import logging
17
 
import urlparse
18
 
 
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 _
25
 
import heatclient
26
 
import horizon.forms
27
 
from horizon import messages
28
 
 
29
 
from tuskar_ui import api
30
 
from tuskar_ui.infrastructure.overview import forms
31
 
from tuskar_ui.infrastructure import views
32
 
 
33
 
 
34
 
INDEX_URL = 'horizon:infrastructure:overview:index'
35
 
 
36
 
LOG = logging.getLogger(__name__)
37
 
 
38
 
 
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)
43
 
 
44
 
 
45
 
def _get_role_data(plan, stack, form, role):
46
 
    """Gathers data about a single deployment role.
47
 
 
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
50
 
    from the template.
51
 
 
52
 
    """
53
 
    data = {
54
 
        'id': role.id,
55
 
        'role': role,
56
 
        'name': role.name,
57
 
        'planned_node_count': plan.get_role_node_count(role),
58
 
        'field': form['%s-count' % role.id] if form else '',
59
 
    }
60
 
 
61
 
    if stack:
62
 
        resources = stack.resources(role=role, with_joins=True)
63
 
        nodes = [r.node for r in resources]
64
 
        node_count = len(nodes)
65
 
 
66
 
        deployed_node_count = 0
67
 
        deploying_node_count = 0
68
 
        error_node_count = 0
69
 
        waiting_node_count = node_count
70
 
 
71
 
        status = 'warning'
72
 
        if nodes:
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)
81
 
 
82
 
            if error_node_count or 'FAILED' in stack.stack_status:
83
 
                status = 'danger'
84
 
            elif deployed_node_count == data['planned_node_count']:
85
 
                status = 'success'
86
 
            else:
87
 
                status = 'info'
88
 
 
89
 
        finished = deployed_node_count == data['planned_node_count']
90
 
        if finished:
91
 
            icon = 'fa-check'
92
 
        elif status in ('danger', 'warning'):
93
 
            icon = 'fa-exclamation'
94
 
        else:
95
 
            icon = 'fa-spinner fa-spin'
96
 
 
97
 
        data.update({
98
 
            'status': status,
99
 
            'finished': finished,
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,
105
 
            'icon': icon,
106
 
        })
107
 
 
108
 
    # TODO(rdopieralski) get this from ceilometer
109
 
    # data['capacity'] = 20
110
 
    return data
111
 
 
112
 
 
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)
117
 
 
118
 
    def get_progress_update(self, request, data):
119
 
        return {
120
 
            'progress': data.get('progress'),
121
 
            'show_last_events': data.get('show_last_events'),
122
 
            'last_events_title': unicode(data.get('last_events_title')),
123
 
            'last_events': [{
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', [])],
129
 
            'roles': [{
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', [])],
143
 
        }
144
 
 
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',
152
 
            )
153
 
        return super(IndexView, self).get(request, *args, **kwargs)
154
 
 
155
 
    def get_form(self, form_class):
156
 
        return form_class(self.request, **self.get_form_kwargs())
157
 
 
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))
161
 
        return context
162
 
 
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')
167
 
 
168
 
        context['plan'] = plan
169
 
        context['stack'] = stack
170
 
 
171
 
        roles = [_get_role_data(plan, stack, form, role)
172
 
                 for role in plan.role_list]
173
 
        context['roles'] = roles
174
 
 
175
 
        if stack:
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:]
180
 
 
181
 
            if failed_events:
182
 
                context['last_events_title'] = _('Last failed events')
183
 
                context['last_events'] = failed_events
184
 
            else:
185
 
                context['last_events_title'] = _('Last event')
186
 
                context['last_events'] = [stack.events[0]]
187
 
 
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
193
 
                # workable.
194
 
                total_num_nodes_count = 10
195
 
 
196
 
                try:
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
202
 
                    # undeploying.
203
 
                    resources_count = total_num_nodes_count
204
 
 
205
 
                # TODO(lsmola) same as hack above
206
 
                total_num_nodes_count = max(
207
 
                    resources_count, total_num_nodes_count)
208
 
 
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)
216
 
                ))
217
 
            else:
218
 
                # stack is active
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')
225
 
 
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
231
 
        else:
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)
237
 
        return context
238
 
 
239
 
    def post(self, request, *args, **kwargs):
240
 
        """If the post comes from ajax, return validation results as json."""
241
 
 
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)
246
 
        if form.is_valid():
247
 
            handled = form.handle(self.request, form.cleaned_data)
248
 
        else:
249
 
            handled = False
250
 
        if handled:
251
 
            messages = forms.validate_plan(request, form.plan)
252
 
        else:
253
 
            messages = [{
254
 
                'text': _(u"Error saving the plan."),
255
 
                'is_critical': True,
256
 
            }]
257
 
            messages.extend({
258
 
                'text': repr(error),
259
 
            } for error in form.non_field_errors)
260
 
            messages.extend({
261
 
                'text': repr(error),
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),
267
 
            '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')
274
 
 
275
 
 
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")
280
 
 
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)
285
 
 
286
 
        context['autogenerated_parameters'] = (
287
 
            plan.list_generated_parameters(with_prefix=False).keys())
288
 
        return context
289
 
 
290
 
    def get_success_url(self):
291
 
        return reverse(INDEX_URL)
292
 
 
293
 
 
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")
298
 
 
299
 
    def get_success_url(self):
300
 
        return reverse(INDEX_URL)
301
 
 
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
306
 
        return context
307
 
 
308
 
    def get_initial(self, **kwargs):
309
 
        initial = super(UndeployConfirmationView, self).get_initial(**kwargs)
310
 
        initial['stack_id'] = self.get_stack().id
311
 
        return initial
312
 
 
313
 
 
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")
318
 
 
319
 
    def get_success_url(self):
320
 
        return reverse(INDEX_URL)
321
 
 
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
326
 
        return context
327
 
 
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', '')
332
 
        return initial
333
 
 
334
 
 
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")
339
 
 
340
 
    def get_success_url(self):
341
 
        return reverse(INDEX_URL)
342
 
 
343
 
    def get_form(self, form_class):
344
 
        return form_class(self.request, **self.get_form_kwargs())
345
 
 
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]
352
 
        context.update({
353
 
            'roles': roles,
354
 
            'plan': plan,
355
 
        })
356
 
        return context
357
 
 
358
 
 
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))
370
 
    return credentials
371
 
 
372
 
 
373
 
def download_overcloudrc_file(request):
374
 
    template = 'infrastructure/overview/overcloudrc.sh.template'
375
 
    try:
376
 
        context = _get_openrc_credentials(request)
377
 
 
378
 
        response = shortcuts.render(request,
379
 
                                    template,
380
 
                                    context,
381
 
                                    content_type="text/plain")
382
 
        response['Content-Disposition'] = ('attachment; '
383
 
                                           'filename="overcloudrc"')
384
 
        response['Content-Length'] = str(len(response.content))
385
 
        return response
386
 
 
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())