"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.

    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"]}