"Fossies" - the Fresh Open Source Software Archive 
Member "horizon-20.1.2/openstack_dashboard/dashboards/project/instances/views.py" (29 Apr 2022, 30528 Bytes) of package /linux/misc/openstack/horizon-20.1.2.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style:
standard) with prefixed line numbers.
Alternatively you can here
view or
download the uninterpreted source code file.
See also the latest
Fossies "Diffs" side-by-side code changes report for "views.py":
20.1.1_vs_20.1.2.
1 # Copyright 2012 United States Government as represented by the
2 # Administrator of the National Aeronautics and Space Administration.
3 # All Rights Reserved.
4 #
5 # Copyright 2012 Nebula, Inc.
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License"); you may
8 # not use this file except in compliance with the License. You may obtain
9 # a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 # License for the specific language governing permissions and limitations
17 # under the License.
18
19 """
20 Views for managing instances.
21 """
22 from collections import OrderedDict
23 import logging
24
25 from django.conf import settings
26 from django import http
27 from django import shortcuts
28 from django.urls import reverse
29 from django.urls import reverse_lazy
30 from django.utils.translation import ugettext_lazy as _
31 from django.views import generic
32
33 from horizon import exceptions
34 from horizon import forms
35 from horizon import messages
36 from horizon import tables
37 from horizon import tabs
38 from horizon.utils import memoized
39 from horizon import workflows
40
41 from openstack_dashboard import api
42 from openstack_dashboard.utils import filters
43 from openstack_dashboard.utils import settings as setting_utils
44
45 from openstack_dashboard.dashboards.project.instances \
46 import console as project_console
47 from openstack_dashboard.dashboards.project.instances \
48 import forms as project_forms
49 from openstack_dashboard.dashboards.project.instances \
50 import tables as project_tables
51 from openstack_dashboard.dashboards.project.instances \
52 import tabs as project_tabs
53 from openstack_dashboard.dashboards.project.instances \
54 import utils as instance_utils
55 from openstack_dashboard.dashboards.project.instances \
56 import workflows as project_workflows
57 from openstack_dashboard.dashboards.project.networks.ports \
58 import views as port_views
59 from openstack_dashboard.utils import futurist_utils
60 from openstack_dashboard.views import get_url_with_pagination
61
62 LOG = logging.getLogger(__name__)
63
64
65 class IndexView(tables.PagedTableMixin, tables.DataTableView):
66 table_class = project_tables.InstancesTable
67 page_title = _("Instances")
68
69 def has_prev_data(self, table):
70 return getattr(self, "_prev", False)
71
72 def has_more_data(self, table):
73 return self._more
74
75 def _get_flavors(self):
76 # Gather our flavors to correlate our instances to them
77 try:
78 flavors = api.nova.flavor_list(self.request)
79 return dict((str(flavor.id), flavor) for flavor in flavors)
80 except Exception:
81 exceptions.handle(self.request, ignore=True)
82 return {}
83
84 def _get_images(self):
85 # Gather our images to correlate our instances to them
86 try:
87 # Community images have to be retrieved separately and merged,
88 # because their visibility has to be explicitly defined in the
89 # API call and the Glance API currently does not support filtering
90 # by multiple values in the visibility field.
91 # TODO(gabriel): Handle pagination.
92 images = api.glance.image_list_detailed(self.request)[0]
93 community_images = api.glance.image_list_detailed(
94 self.request, filters={'visibility': 'community'})[0]
95 image_map = {
96 image.id: image for image in images
97 }
98 # Images have to be filtered by their uuids; some users
99 # have default access to certain community images.
100 for image in community_images:
101 image_map.setdefault(image.id, image)
102 return image_map
103 except Exception:
104 exceptions.handle(self.request, ignore=True)
105 return {}
106
107 def _get_instances(self, search_opts, sort_dir):
108 try:
109 instances, self._more, self._prev = api.nova.server_list_paged(
110 self.request,
111 search_opts=search_opts,
112 sort_dir=sort_dir)
113 except Exception:
114 self._more = self._prev = False
115 instances = []
116 exceptions.handle(self.request,
117 _('Unable to retrieve instances.'))
118
119 # In case of exception when calling nova.server_list
120 # don't call api.network
121 if not instances:
122 return []
123
124 # TODO(future): Explore more efficient logic to sync IP address
125 # and drop the setting OPENSTACK_INSTANCE_RETRIEVE_IP_ADDRESSES.
126 # The situation servers_update_addresses() is needed is only
127 # when IP address of a server is updated via neutron API and
128 # nova network info cache is not synced. Precisely there is no
129 # need to check IP addresses of all servers. It is sufficient to
130 # fetch IP address information for servers recently updated.
131 if not settings.OPENSTACK_INSTANCE_RETRIEVE_IP_ADDRESSES:
132 return instances
133 try:
134 api.network.servers_update_addresses(self.request, instances)
135 except Exception:
136 exceptions.handle(
137 self.request,
138 message=_('Unable to retrieve IP addresses from Neutron.'),
139 ignore=True)
140
141 return instances
142
143 def _get_volumes(self):
144 # Gather our volumes to get their image metadata for instance
145 try:
146 volumes = api.cinder.volume_list(self.request)
147 return dict((str(volume.id), volume) for volume in volumes)
148 except Exception:
149 exceptions.handle(self.request, ignore=True)
150 return {}
151
152 def get_data(self):
153 marker, sort_dir = self._get_marker()
154 search_opts = self.get_filters({'marker': marker, 'paginate': True})
155
156 image_dict, flavor_dict, volume_dict = \
157 futurist_utils.call_functions_parallel(
158 self._get_images, self._get_flavors, self._get_volumes
159 )
160
161 non_api_filter_info = (
162 ('image_name', 'image', image_dict.values()),
163 ('flavor_name', 'flavor', flavor_dict.values()),
164 )
165 if not process_non_api_filters(search_opts, non_api_filter_info):
166 self._more = False
167 return []
168
169 instances = self._get_instances(search_opts, sort_dir)
170
171 # Loop through instances to get flavor info.
172 for instance in instances:
173 self._populate_image_info(instance, image_dict, volume_dict)
174
175 flavor_id = instance.flavor["id"]
176 if flavor_id in flavor_dict:
177 instance.full_flavor = flavor_dict[flavor_id]
178 else:
179 # If the flavor_id is not in flavor_dict,
180 # put info in the log file.
181 LOG.info('Unable to retrieve flavor "%s" for instance "%s".',
182 flavor_id, instance.id)
183
184 return instances
185
186 def _populate_image_info(self, instance, image_dict, volume_dict):
187 if not hasattr(instance, 'image'):
188 return
189 # Instance from image returns dict
190 if isinstance(instance.image, dict):
191 image_id = instance.image.get('id')
192 if image_id in image_dict:
193 instance.image = image_dict[image_id]
194 # In case image not found in image_dict, set name to empty
195 # to avoid fallback API call to Glance in api/nova.py
196 # until the call is deprecated in api itself
197 else:
198 instance.image['name'] = _("-")
199 # Otherwise trying to get image from volume metadata
200 else:
201 instance_volumes = [
202 attachment
203 for volume in volume_dict.values()
204 for attachment in volume.attachments
205 if attachment['server_id'] == instance.id
206 ]
207 # While instance from volume is being created,
208 # it does not have volumes
209 if not instance_volumes:
210 return
211 # Sorting attached volumes by device name (eg '/dev/sda')
212 instance_volumes.sort(key=lambda attach: attach['device'])
213 # Getting volume object, which is as attached
214 # as the first device
215 boot_volume = volume_dict[instance_volumes[0]['id']]
216 # There is a case where volume_image_metadata contains
217 # only fields other than 'image_id' (See bug 1834747),
218 # so we try to populate image information only when it is found.
219 volume_metadata = getattr(boot_volume, "volume_image_metadata", {})
220 image_id = volume_metadata.get('image_id')
221 if image_id:
222 try:
223 instance.image = image_dict[image_id]
224 except KeyError:
225 # KeyError occurs when volume was created from image and
226 # then this image is deleted.
227 pass
228
229
230 def process_non_api_filters(search_opts, non_api_filter_info):
231 """Process filters by non-API fields
232
233 There are cases where it is useful to provide a filter field
234 which does not exist in a resource in a backend service.
235 For example, nova server list provides 'image' field with image ID
236 but 'image name' is more useful for GUI users.
237 This function replaces fake fields into corresponding real fields.
238
239 The format of non_api_filter_info is a tuple/list of
240 (fake_field, real_field, resources).
241
242 This returns True if further lookup is required.
243 It returns False if there are no matching resources,
244 for example, if no corresponding real field exists.
245 """
246 for fake_field, real_field, resources in non_api_filter_info:
247 if not _swap_filter(resources, search_opts, fake_field, real_field):
248 return False
249 return True
250
251
252 def _swap_filter(resources, search_opts, fake_field, real_field):
253 if fake_field not in search_opts:
254 return True
255 filter_string = search_opts[fake_field]
256 matched = [resource for resource in resources
257 if resource.name.lower() == filter_string.lower()]
258 if not matched:
259 return False
260 search_opts[real_field] = matched[0].id
261 del search_opts[fake_field]
262 return True
263
264
265 class LaunchInstanceView(workflows.WorkflowView):
266 workflow_class = project_workflows.LaunchInstance
267
268 def __init__(self):
269 super().__init__()
270 LOG.warning('Django version of the launch instance form is '
271 'deprecated since Wallaby release. Switch to '
272 'the AngularJS version of the form by setting '
273 'LAUNCH_INSTANCE_NG_ENABLED to True and '
274 'LAUNCH_INSTANCE_LEGACY_ENABLED to False.')
275
276 def get_initial(self):
277 initial = super().get_initial()
278 initial['project_id'] = self.request.user.tenant_id
279 initial['user_id'] = self.request.user.id
280 initial['config_drive'] = setting_utils.get_dict_config(
281 'LAUNCH_INSTANCE_DEFAULTS', 'config_drive')
282 return initial
283
284
285 # TODO(stephenfin): Migrate to CBV
286 def console(request, instance_id):
287 data = _('Unable to get log for instance "%s".') % instance_id
288 tail = request.GET.get('length')
289 if tail and not tail.isdigit():
290 msg = _('Log length must be a nonnegative integer.')
291 messages.warning(request, msg)
292 else:
293 try:
294 data = api.nova.server_console_output(request,
295 instance_id,
296 tail_length=tail)
297 except Exception:
298 exceptions.handle(request, ignore=True)
299 return http.HttpResponse(data.encode('utf-8'), content_type='text/plain')
300
301
302 # TODO(stephenfin): Migrate to CBV
303 def auto_console(request, instance_id):
304 console_type = settings.CONSOLE_TYPE
305 try:
306 instance = api.nova.server_get(request, instance_id)
307 console_url = project_console.get_console(request, console_type,
308 instance)[1]
309 return shortcuts.redirect(console_url)
310 except Exception:
311 redirect = reverse("horizon:project:instances:index")
312 msg = _('Unable to get console for instance "%s".') % instance_id
313 exceptions.handle(request, msg, redirect=redirect)
314
315
316 # TODO(stephenfin): Migrate to CBV
317 def vnc(request, instance_id):
318 try:
319 instance = api.nova.server_get(request, instance_id)
320 console_url = project_console.get_console(request, 'VNC', instance)[1]
321 return shortcuts.redirect(console_url)
322 except Exception:
323 redirect = reverse("horizon:project:instances:index")
324 msg = _('Unable to get VNC console for instance "%s".') % instance_id
325 exceptions.handle(request, msg, redirect=redirect)
326
327
328 # TODO(stephenfin): Migrate to CBV
329 def mks(request, instance_id):
330 try:
331 instance = api.nova.server_get(request, instance_id)
332 console_url = project_console.get_console(request, 'MKS', instance)[1]
333 return shortcuts.redirect(console_url)
334 except Exception:
335 redirect = reverse("horizon:project:instances:index")
336 msg = _('Unable to get MKS console for instance "%s".') % instance_id
337 exceptions.handle(request, msg, redirect=redirect)
338
339
340 # TODO(stephenfin): Migrate to CBV
341 def spice(request, instance_id):
342 try:
343 instance = api.nova.server_get(request, instance_id)
344 console_url = project_console.get_console(request, 'SPICE',
345 instance)[1]
346 return shortcuts.redirect(console_url)
347 except Exception:
348 redirect = reverse("horizon:project:instances:index")
349 msg = _('Unable to get SPICE console for instance "%s".') % instance_id
350 exceptions.handle(request, msg, redirect=redirect)
351
352
353 # TODO(stephenfin): Migrate to CBV
354 def rdp(request, instance_id):
355 try:
356 instance = api.nova.server_get(request, instance_id)
357 console_url = project_console.get_console(request, 'RDP', instance)[1]
358 return shortcuts.redirect(console_url)
359 except Exception:
360 redirect = reverse("horizon:project:instances:index")
361 msg = _('Unable to get RDP console for instance "%s".') % instance_id
362 exceptions.handle(request, msg, redirect=redirect)
363
364
365 class SerialConsoleView(generic.TemplateView):
366 template_name = 'serial_console.html'
367
368 def get_context_data(self, **kwargs):
369 context = super().get_context_data(**kwargs)
370 instance = None
371 try:
372 instance = api.nova.server_get(self.request,
373 self.kwargs['instance_id'])
374 except Exception:
375 context["error_message"] = _(
376 "Cannot find instance %s.") % self.kwargs['instance_id']
377 # name is unknown, so leave it blank for the window title
378 # in full-screen mode, so only the instance id is shown.
379 context['page_title'] = self.kwargs['instance_id']
380 return context
381 context['page_title'] = "%s (%s)" % (instance.name, instance.id)
382 try:
383 console_url = project_console.get_console(self.request,
384 "SERIAL", instance)[1]
385 context["console_url"] = console_url
386 context["protocols"] = "['binary', 'base64']"
387 except exceptions.NotAvailable:
388 context["error_message"] = _(
389 "Cannot get console for instance %s.") % self.kwargs[
390 'instance_id']
391 return context
392
393
394 class UpdateView(workflows.WorkflowView):
395 workflow_class = project_workflows.UpdateInstance
396 success_url = reverse_lazy("horizon:project:instances:index")
397
398 def get_context_data(self, **kwargs):
399 context = super().get_context_data(**kwargs)
400 context["instance_id"] = self.kwargs['instance_id']
401 return context
402
403 @memoized.memoized_method
404 def get_object(self, *args, **kwargs):
405 instance_id = self.kwargs['instance_id']
406 try:
407 return api.nova.server_get(self.request, instance_id)
408 except Exception:
409 redirect = reverse("horizon:project:instances:index")
410 msg = _('Unable to retrieve instance details.')
411 exceptions.handle(self.request, msg, redirect=redirect)
412
413 def get_initial(self):
414 initial = super().get_initial()
415 instance = self.get_object()
416 initial.update({'instance_id': self.kwargs['instance_id'],
417 'name': getattr(instance, 'name', ''),
418 'description': getattr(instance, 'description', ''),
419 'target_tenant_id': self.request.user.tenant_id})
420 return initial
421
422
423 class RebuildView(forms.ModalFormView):
424 form_class = project_forms.RebuildInstanceForm
425 template_name = 'project/instances/rebuild.html'
426 success_url = reverse_lazy('horizon:project:instances:index')
427 page_title = _("Rebuild Instance")
428 submit_label = page_title
429
430 def get_context_data(self, **kwargs):
431 context = super().get_context_data(**kwargs)
432 context['instance_id'] = self.kwargs['instance_id']
433 context['can_set_server_password'] = api.nova.can_set_server_password()
434 return context
435
436 @memoized.memoized_method
437 def get_object(self, *args, **kwargs):
438 instance_id = self.kwargs['instance_id']
439 try:
440 return api.nova.server_get(self.request, instance_id)
441 except Exception:
442 redirect = reverse("horizon:project:instances:index")
443 msg = _('Unable to retrieve instance details.')
444 exceptions.handle(self.request, msg, redirect=redirect)
445
446 def get_initial(self):
447 instance = self.get_object()
448 initial = {'instance_id': self.kwargs['instance_id'],
449 'description': getattr(instance, 'description', '')}
450 return initial
451
452
453 class DecryptPasswordView(forms.ModalFormView):
454 form_class = project_forms.DecryptPasswordInstanceForm
455 template_name = 'project/instances/decryptpassword.html'
456 success_url = reverse_lazy('horizon:project:instances:index')
457 page_title = _("Retrieve Instance Password")
458
459 def get_context_data(self, **kwargs):
460 context = super().get_context_data(**kwargs)
461 context['instance_id'] = self.kwargs['instance_id']
462 context['keypair_name'] = self.kwargs['keypair_name']
463 return context
464
465 def get_initial(self):
466 return {'instance_id': self.kwargs['instance_id'],
467 'keypair_name': self.kwargs['keypair_name']}
468
469
470 class DisassociateView(forms.ModalFormView):
471 form_class = project_forms.Disassociate
472 template_name = 'project/instances/disassociate.html'
473 success_url = reverse_lazy('horizon:project:instances:index')
474 page_title = _("Disassociate floating IP")
475 submit_label = _("Disassociate")
476
477 def get_context_data(self, **kwargs):
478 context = super().get_context_data(**kwargs)
479 context['instance_id'] = self.kwargs['instance_id']
480 return context
481
482 def get_initial(self):
483 return {'instance_id': self.kwargs['instance_id']}
484
485
486 class DetailView(tabs.TabView):
487 tab_group_class = project_tabs.InstanceDetailTabs
488 template_name = 'horizon/common/_detail.html'
489 redirect_url = 'horizon:project:instances:index'
490 page_title = "{{ instance.name|default:instance.id }}"
491 image_url = 'horizon:project:images:images:detail'
492 volume_url = 'horizon:project:volumes:detail'
493
494 def get_context_data(self, **kwargs):
495 context = super().get_context_data(**kwargs)
496 instance = self.get_data()
497 if instance.image:
498 instance.image_url = reverse(self.image_url,
499 args=[instance.image['id']])
500 instance.volume_url = self.volume_url
501 context["instance"] = instance
502 context["url"] = get_url_with_pagination(
503 self.request,
504 project_tables.InstancesTable._meta.pagination_param,
505 project_tables.InstancesTable._meta.prev_pagination_param,
506 self.redirect_url)
507
508 context["actions"] = self._get_actions(instance)
509 return context
510
511 def _get_actions(self, instance):
512 table = project_tables.InstancesTable(self.request)
513 return table.render_row_actions(instance)
514
515 def _get_volumes(self, instance):
516 instance_id = instance.id
517 try:
518 instance.volumes = api.nova.instance_volumes_list(self.request,
519 instance_id)
520 # Sort by device name
521 instance.volumes.sort(key=lambda vol: vol.device)
522 except Exception:
523 msg = _('Unable to retrieve volume list for instance '
524 '"%(name)s" (%(id)s).') % {'name': instance.name,
525 'id': instance_id}
526 exceptions.handle(self.request, msg, ignore=True)
527
528 def _get_flavor(self, instance):
529 instance_id = instance.id
530 try:
531 instance.full_flavor = api.nova.flavor_get(
532 self.request, instance.flavor["id"])
533 except Exception:
534 msg = _('Unable to retrieve flavor information for instance '
535 '"%(name)s" (%(id)s).') % {'name': instance.name,
536 'id': instance_id}
537 exceptions.handle(self.request, msg, ignore=True)
538
539 def _get_security_groups(self, instance):
540 instance_id = instance.id
541 try:
542 instance.security_groups = api.neutron.server_security_groups(
543 self.request, instance_id)
544 except Exception:
545 msg = _('Unable to retrieve security groups for instance '
546 '"%(name)s" (%(id)s).') % {'name': instance.name,
547 'id': instance_id}
548 exceptions.handle(self.request, msg, ignore=True)
549
550 def _update_addresses(self, instance):
551 instance_id = instance.id
552 try:
553 api.network.servers_update_addresses(self.request, [instance])
554 except Exception:
555 msg = _('Unable to retrieve IP addresses from Neutron for '
556 'instance "%(name)s" (%(id)s).') \
557 % {'name': instance.name, 'id': instance_id}
558 exceptions.handle(self.request, msg, ignore=True)
559
560 @memoized.memoized_method
561 def get_data(self):
562 instance_id = self.kwargs['instance_id']
563
564 try:
565 instance = api.nova.server_get(self.request, instance_id)
566 except Exception:
567 redirect = reverse(self.redirect_url)
568 exceptions.handle(self.request,
569 _('Unable to retrieve details for '
570 'instance "%s".') % instance_id,
571 redirect=redirect)
572 # Not all exception types handled above will result in a redirect.
573 # Need to raise here just in case.
574 raise exceptions.Http302(redirect)
575
576 choices = project_tables.STATUS_DISPLAY_CHOICES
577 instance.status_label = (
578 filters.get_display_label(choices, instance.status))
579
580 futurist_utils.call_functions_parallel(
581 (self._get_volumes, [instance]),
582 (self._get_flavor, [instance]),
583 (self._get_security_groups, [instance]),
584 (self._update_addresses, [instance]),
585 )
586
587 return instance
588
589 def get_tabs(self, request, *args, **kwargs):
590 instance = self.get_data()
591 return self.tab_group_class(request, instance=instance, **kwargs)
592
593
594 class ResizeView(workflows.WorkflowView):
595 workflow_class = project_workflows.ResizeInstance
596 success_url = reverse_lazy("horizon:project:instances:index")
597
598 def get_context_data(self, **kwargs):
599 context = super().get_context_data(**kwargs)
600 context["instance_id"] = self.kwargs['instance_id']
601 return context
602
603 @memoized.memoized_method
604 def get_object(self, *args, **kwargs):
605 instance_id = self.kwargs['instance_id']
606 try:
607 instance = api.nova.server_get(self.request, instance_id)
608 except Exception:
609 redirect = reverse("horizon:project:instances:index")
610 msg = _('Unable to retrieve instance details.')
611 exceptions.handle(self.request, msg, redirect=redirect)
612 flavors = self.get_flavors()
613 flavor = instance_utils.resolve_flavor(self.request, instance, flavors)
614 instance.flavor_name = flavor.name
615 return instance
616
617 @memoized.memoized_method
618 def get_flavors(self, *args, **kwargs):
619 try:
620 flavors = api.nova.flavor_list(self.request)
621 return OrderedDict((str(flavor.id), flavor) for flavor in flavors)
622 except Exception:
623 redirect = reverse("horizon:project:instances:index")
624 exceptions.handle(self.request,
625 _('Unable to retrieve flavors.'),
626 redirect=redirect)
627
628 def get_initial(self):
629 initial = super().get_initial()
630 _object = self.get_object()
631 if _object:
632 initial.update(
633 {'instance_id': self.kwargs['instance_id'],
634 'name': getattr(_object, 'name', None),
635 'old_flavor_name': getattr(_object, 'flavor_name', ''),
636 'flavors': self.get_flavors()})
637 return initial
638
639
640 class AttachInterfaceView(forms.ModalFormView):
641 form_class = project_forms.AttachInterface
642 template_name = 'project/instances/attach_interface.html'
643 page_title = _("Attach Interface")
644 form_id = "attach_interface_form"
645 submit_label = _("Attach Interface")
646 success_url = reverse_lazy('horizon:project:instances:index')
647
648 def get_context_data(self, **kwargs):
649 context = super().get_context_data(**kwargs)
650 context['instance_id'] = self.kwargs['instance_id']
651 return context
652
653 def get_initial(self):
654 args = {'instance_id': self.kwargs['instance_id']}
655 submit_url = "horizon:project:instances:attach_interface"
656 self.submit_url = reverse(submit_url, kwargs=args)
657 return args
658
659
660 class AttachVolumeView(forms.ModalFormView):
661 form_class = project_forms.AttachVolume
662 template_name = 'project/instances/attach_volume.html'
663 page_title = _("Attach Volume")
664 modal_id = "attach_volume_modal"
665 submit_label = _("Attach Volume")
666 success_url = reverse_lazy('horizon:project:instances:index')
667
668 def get_initial(self):
669 args = {'instance_id': self.kwargs['instance_id']}
670 submit_url = "horizon:project:instances:attach_volume"
671 self.submit_url = reverse(submit_url, kwargs=args)
672 try:
673 volume_list = api.cinder.volume_list(self.request)
674 except Exception:
675 volume_list = []
676 exceptions.handle(self.request,
677 _("Unable to retrieve volume information."))
678 return {"instance_id": self.kwargs["instance_id"],
679 "volume_list": volume_list}
680
681 def get_context_data(self, **kwargs):
682 context = super().get_context_data(**kwargs)
683 context['instance_id'] = self.kwargs['instance_id']
684 return context
685
686
687 class DetachVolumeView(forms.ModalFormView):
688 form_class = project_forms.DetachVolume
689 template_name = 'project/instances/detach_volume.html'
690 page_title = _("Detach Volume")
691 modal_id = "detach_volume_modal"
692 submit_label = _("Detach Volume")
693 success_url = reverse_lazy('horizon:project:instances:index')
694
695 def get_initial(self):
696 args = {'instance_id': self.kwargs['instance_id']}
697 submit_url = "horizon:project:instances:detach_volume"
698 self.submit_url = reverse(submit_url, kwargs=args)
699 return {"instance_id": self.kwargs["instance_id"]}
700
701 def get_context_data(self, **kwargs):
702 context = super().get_context_data(**kwargs)
703 context['instance_id'] = self.kwargs['instance_id']
704 return context
705
706
707 class DetachInterfaceView(forms.ModalFormView):
708 form_class = project_forms.DetachInterface
709 template_name = 'project/instances/detach_interface.html'
710 page_title = _("Detach Interface")
711 form_id = "detach_interface_form"
712 submit_label = _("Detach Interface")
713 success_url = reverse_lazy('horizon:project:instances:index')
714
715 def get_context_data(self, **kwargs):
716 context = super().get_context_data(**kwargs)
717 context['instance_id'] = self.kwargs['instance_id']
718 return context
719
720 def get_initial(self):
721 args = {"instance_id": self.kwargs["instance_id"]}
722 submit_url = "horizon:project:instances:detach_interface"
723 self.submit_url = reverse(submit_url, kwargs=args)
724 return args
725
726
727 class UpdatePortView(port_views.UpdateView):
728 workflow_class = project_workflows.UpdatePort
729 failure_url = 'horizon:project:instances:detail'
730
731 @memoized.memoized_method
732 def _get_object(self, *args, **kwargs):
733 port_id = self.kwargs['port_id']
734 try:
735 return api.neutron.port_get(self.request, port_id)
736 except Exception:
737 redirect = reverse(self.failure_url,
738 args=(self.kwargs['instance_id'],))
739 msg = _('Unable to retrieve port details')
740 exceptions.handle(self.request, msg, redirect=redirect)
741
742 def get_initial(self):
743 initial = super().get_initial()
744 initial['instance_id'] = self.kwargs['instance_id']
745 return initial
746
747
748 class RescueView(forms.ModalFormView):
749 form_class = project_forms.RescueInstanceForm
750 template_name = 'project/instances/rescue.html'
751 submit_label = _("Confirm")
752 submit_url = "horizon:project:instances:rescue"
753 success_url = reverse_lazy('horizon:project:instances:index')
754 page_title = _("Rescue Instance")
755
756 def get_context_data(self, **kwargs):
757 context = super().get_context_data(**kwargs)
758 context["instance_id"] = self.kwargs['instance_id']
759 args = (self.kwargs['instance_id'],)
760 context['submit_url'] = reverse(self.submit_url, args=args)
761 return context
762
763 def get_initial(self):
764 return {'instance_id': self.kwargs["instance_id"]}