"Fossies" - the Fresh Open Source Software Archive

Member "horizon-20.1.2/openstack_dashboard/dashboards/project/instances/tables.py" (29 Apr 2022, 48591 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 Nebula, Inc.
    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 
   16 import logging
   17 
   18 from django.conf import settings
   19 from django.http import HttpResponse
   20 from django import shortcuts
   21 from django import template
   22 from django.template.defaultfilters import title
   23 from django import urls
   24 from django.utils.http import urlencode
   25 from django.utils.safestring import mark_safe
   26 from django.utils.text import format_lazy
   27 from django.utils.translation import npgettext_lazy
   28 from django.utils.translation import pgettext_lazy
   29 from django.utils.translation import ugettext_lazy as _
   30 from django.utils.translation import ungettext_lazy
   31 import netaddr
   32 
   33 from horizon import exceptions
   34 from horizon import messages
   35 from horizon import tables
   36 from horizon.templatetags import sizeformat
   37 from horizon.utils import filters
   38 
   39 from openstack_dashboard import api
   40 from openstack_dashboard.dashboards.project.floating_ips import workflows
   41 from openstack_dashboard.dashboards.project.instances import tabs
   42 from openstack_dashboard.dashboards.project.instances \
   43     import utils as instance_utils
   44 from openstack_dashboard.dashboards.project.instances.workflows \
   45     import resize_instance
   46 from openstack_dashboard.dashboards.project.instances.workflows \
   47     import update_instance
   48 from openstack_dashboard import policy
   49 from openstack_dashboard.views import get_url_with_pagination
   50 
   51 LOG = logging.getLogger(__name__)
   52 
   53 ACTIVE_STATES = ("ACTIVE",)
   54 VOLUME_ATTACH_READY_STATES = ("ACTIVE", "SHUTOFF")
   55 SNAPSHOT_READY_STATES = ("ACTIVE", "SHUTOFF", "PAUSED", "SUSPENDED")
   56 SHELVE_READY_STATES = ("ACTIVE", "SHUTOFF", "PAUSED", "SUSPENDED")
   57 
   58 POWER_STATES = {
   59     0: "NO STATE",
   60     1: "RUNNING",
   61     2: "BLOCKED",
   62     3: "PAUSED",
   63     4: "SHUTDOWN",
   64     5: "SHUTOFF",
   65     6: "CRASHED",
   66     7: "SUSPENDED",
   67     8: "FAILED",
   68     9: "BUILDING",
   69 }
   70 
   71 PAUSE = 0
   72 UNPAUSE = 1
   73 SUSPEND = 0
   74 RESUME = 1
   75 SHELVE = 0
   76 UNSHELVE = 1
   77 
   78 
   79 def is_deleting(instance):
   80     task_state = getattr(instance, "OS-EXT-STS:task_state", None)
   81     if not task_state:
   82         return False
   83     return task_state.lower() == "deleting"
   84 
   85 
   86 class DeleteInstance(policy.PolicyTargetMixin, tables.DeleteAction):
   87     policy_rules = (("compute", "os_compute_api:servers:delete"),)
   88     help_text = _("Deleted instances are not recoverable.")
   89     default_message_level = "info"
   90 
   91     @staticmethod
   92     def action_present(count):
   93         return ungettext_lazy(
   94             "Delete Instance",
   95             "Delete Instances",
   96             count
   97         )
   98 
   99     @staticmethod
  100     def action_past(count):
  101         return ungettext_lazy(
  102             "Scheduled deletion of Instance",
  103             "Scheduled deletion of Instances",
  104             count
  105         )
  106 
  107     def allowed(self, request, instance=None):
  108         error_state = False
  109         if instance:
  110             error_state = (instance.status == 'ERROR')
  111         return error_state or not is_deleting(instance)
  112 
  113     def action(self, request, obj_id):
  114         api.nova.server_delete(request, obj_id)
  115 
  116 
  117 class RebootInstance(policy.PolicyTargetMixin, tables.BatchAction):
  118     name = "reboot"
  119     classes = ('btn-reboot',)
  120     policy_rules = (("compute", "os_compute_api:servers:reboot"),)
  121     help_text = _("Restarted instances will lose any data"
  122                   " not saved in persistent storage.")
  123     action_type = "danger"
  124 
  125     @staticmethod
  126     def action_present(count):
  127         return ungettext_lazy(
  128             "Hard Reboot Instance",
  129             "Hard Reboot Instances",
  130             count
  131         )
  132 
  133     @staticmethod
  134     def action_past(count):
  135         return ungettext_lazy(
  136             "Hard Rebooted Instance",
  137             "Hard Rebooted Instances",
  138             count
  139         )
  140 
  141     def allowed(self, request, instance=None):
  142         if instance is None:
  143             return True
  144         return ((instance.status in ACTIVE_STATES or
  145                  instance.status == 'SHUTOFF') and
  146                 not is_deleting(instance))
  147 
  148     def action(self, request, obj_id):
  149         api.nova.server_reboot(request, obj_id, soft_reboot=False)
  150 
  151 
  152 class SoftRebootInstance(RebootInstance):
  153     name = "soft_reboot"
  154 
  155     @staticmethod
  156     def action_present(count):
  157         return ungettext_lazy(
  158             "Soft Reboot Instance",
  159             "Soft Reboot Instances",
  160             count
  161         )
  162 
  163     @staticmethod
  164     def action_past(count):
  165         return ungettext_lazy(
  166             "Soft Rebooted Instance",
  167             "Soft Rebooted Instances",
  168             count
  169         )
  170 
  171     def action(self, request, obj_id):
  172         api.nova.server_reboot(request, obj_id, soft_reboot=True)
  173 
  174     def allowed(self, request, instance=None):
  175         if instance is not None:
  176             return instance.status in ACTIVE_STATES
  177         return True
  178 
  179 
  180 class RescueInstance(policy.PolicyTargetMixin, tables.LinkAction):
  181     name = "rescue"
  182     verbose_name = _("Rescue Instance")
  183     policy_rules = (("compute", "os_compute_api:os-rescue"),)
  184     classes = ("btn-rescue", "ajax-modal")
  185     url = "horizon:project:instances:rescue"
  186 
  187     def get_link_url(self, datum):
  188         instance_id = self.table.get_object_id(datum)
  189         return urls.reverse(self.url, args=[instance_id])
  190 
  191     def allowed(self, request, instance):
  192         return instance.status in ACTIVE_STATES
  193 
  194 
  195 class UnRescueInstance(tables.BatchAction):
  196     name = 'unrescue'
  197     classes = ("btn-unrescue",)
  198 
  199     @staticmethod
  200     def action_present(count):
  201         return ungettext_lazy(
  202             "Unrescue Instance",
  203             "Unrescue Instances",
  204             count
  205         )
  206 
  207     @staticmethod
  208     def action_past(count):
  209         return ungettext_lazy(
  210             "Unrescued Instance",
  211             "Unrescued Instances",
  212             count
  213         )
  214 
  215     def action(self, request, obj_id):
  216         api.nova.server_unrescue(request, obj_id)
  217 
  218     def allowed(self, request, instance=None):
  219         if instance:
  220             return instance.status == "RESCUE"
  221         return False
  222 
  223 
  224 class TogglePause(tables.BatchAction):
  225     name = "pause"
  226     icon = "pause"
  227 
  228     @staticmethod
  229     def action_present(count):
  230         return (
  231             ungettext_lazy(
  232                 "Pause Instance",
  233                 "Pause Instances",
  234                 count
  235             ),
  236             ungettext_lazy(
  237                 "Resume Instance",
  238                 "Resume Instances",
  239                 count
  240             ),
  241         )
  242 
  243     @staticmethod
  244     def action_past(count):
  245         return (
  246             ungettext_lazy(
  247                 "Paused Instance",
  248                 "Paused Instances",
  249                 count
  250             ),
  251             ungettext_lazy(
  252                 "Resumed Instance",
  253                 "Resumed Instances",
  254                 count
  255             ),
  256         )
  257 
  258     def allowed(self, request, instance=None):
  259         if not instance:
  260             return False
  261         self.paused = instance.status == "PAUSED"
  262         if self.paused:
  263             self.current_present_action = UNPAUSE
  264             policy_rules = (
  265                 ("compute", "os_compute_api:os-pause-server:unpause"),)
  266         else:
  267             self.current_present_action = PAUSE
  268             policy_rules = (
  269                 ("compute", "os_compute_api:os-pause-server:pause"),)
  270 
  271         has_permission = policy.check(
  272             policy_rules, request,
  273             target={'project_id': getattr(instance, 'tenant_id', None)})
  274 
  275         return (has_permission and
  276                 (instance.status in ACTIVE_STATES or self.paused) and
  277                 not is_deleting(instance))
  278 
  279     def action(self, request, obj_id):
  280         if self.paused:
  281             api.nova.server_unpause(request, obj_id)
  282             self.current_past_action = UNPAUSE
  283         else:
  284             api.nova.server_pause(request, obj_id)
  285             self.current_past_action = PAUSE
  286 
  287 
  288 class ToggleSuspend(tables.BatchAction):
  289     name = "suspend"
  290     classes = ("btn-suspend",)
  291 
  292     @staticmethod
  293     def action_present(count):
  294         return (
  295             ungettext_lazy(
  296                 "Suspend Instance",
  297                 "Suspend Instances",
  298                 count
  299             ),
  300             ungettext_lazy(
  301                 "Resume Instance",
  302                 "Resume Instances",
  303                 count
  304             ),
  305         )
  306 
  307     @staticmethod
  308     def action_past(count):
  309         return (
  310             ungettext_lazy(
  311                 "Suspended Instance",
  312                 "Suspended Instances",
  313                 count
  314             ),
  315             ungettext_lazy(
  316                 "Resumed Instance",
  317                 "Resumed Instances",
  318                 count
  319             ),
  320         )
  321 
  322     def allowed(self, request, instance=None):
  323         if not instance:
  324             return False
  325         self.suspended = instance.status == "SUSPENDED"
  326         if self.suspended:
  327             self.current_present_action = RESUME
  328             policy_rules = (
  329                 ("compute", "os_compute_api:os-suspend-server:resume"),)
  330         else:
  331             self.current_present_action = SUSPEND
  332             policy_rules = (
  333                 ("compute", "os_compute_api:os-suspend-server:suspend"),)
  334 
  335         has_permission = policy.check(
  336             policy_rules, request,
  337             target={'project_id': getattr(instance, 'tenant_id', None)})
  338 
  339         return (has_permission and
  340                 (instance.status in ACTIVE_STATES or self.suspended) and
  341                 not is_deleting(instance))
  342 
  343     def action(self, request, obj_id):
  344         if self.suspended:
  345             api.nova.server_resume(request, obj_id)
  346             self.current_past_action = RESUME
  347         else:
  348             api.nova.server_suspend(request, obj_id)
  349             self.current_past_action = SUSPEND
  350 
  351 
  352 class ToggleShelve(tables.BatchAction):
  353     name = "shelve"
  354     icon = "shelve"
  355 
  356     @staticmethod
  357     def action_present(count):
  358         return (
  359             ungettext_lazy(
  360                 "Shelve Instance",
  361                 "Shelve Instances",
  362                 count
  363             ),
  364             ungettext_lazy(
  365                 "Unshelve Instance",
  366                 "Unshelve Instances",
  367                 count
  368             ),
  369         )
  370 
  371     @staticmethod
  372     def action_past(count):
  373         return (
  374             ungettext_lazy(
  375                 "Shelved Instance",
  376                 "Shelved Instances",
  377                 count
  378             ),
  379             ungettext_lazy(
  380                 "Unshelved Instance",
  381                 "Unshelved Instances",
  382                 count
  383             ),
  384         )
  385 
  386     def allowed(self, request, instance=None):
  387         if not instance:
  388             return False
  389         if not request.user.is_superuser and getattr(
  390                 instance, 'locked', False):
  391             return False
  392 
  393         self.shelved = instance.status == "SHELVED_OFFLOADED"
  394         if self.shelved:
  395             self.current_present_action = UNSHELVE
  396             policy_rules = (("compute", "os_compute_api:os-shelve:unshelve"),)
  397         else:
  398             self.current_present_action = SHELVE
  399             policy_rules = (("compute", "os_compute_api:os-shelve:shelve"),)
  400 
  401         has_permission = policy.check(
  402             policy_rules, request,
  403             target={'project_id': getattr(instance, 'tenant_id', None)})
  404 
  405         return (has_permission and
  406                 (instance.status in SHELVE_READY_STATES or self.shelved) and
  407                 not is_deleting(instance))
  408 
  409     def action(self, request, obj_id):
  410         if self.shelved:
  411             api.nova.server_unshelve(request, obj_id)
  412             self.current_past_action = UNSHELVE
  413         else:
  414             api.nova.server_shelve(request, obj_id)
  415             self.current_past_action = SHELVE
  416 
  417 
  418 class LaunchLink(tables.LinkAction):
  419     name = "launch"
  420     verbose_name = _("Launch Instance")
  421     url = "horizon:project:instances:launch"
  422     classes = ("ajax-modal", "btn-launch")
  423     icon = "cloud-upload"
  424     policy_rules = (("compute", "os_compute_api:servers:create"),)
  425     ajax = True
  426 
  427     def __init__(self, attrs=None, **kwargs):
  428         kwargs['preempt'] = True
  429         super().__init__(attrs, **kwargs)
  430 
  431     def allowed(self, request, datum):
  432         try:
  433             limits = api.nova.tenant_absolute_limits(request, reserved=True)
  434 
  435             instances_available = limits['maxTotalInstances'] \
  436                 - limits['totalInstancesUsed']
  437             cores_available = limits['maxTotalCores'] \
  438                 - limits['totalCoresUsed']
  439             ram_available = limits['maxTotalRAMSize'] - limits['totalRAMUsed']
  440 
  441             if instances_available <= 0 or cores_available <= 0 \
  442                     or ram_available <= 0:
  443                 if "disabled" not in self.classes:
  444                     self.classes = list(self.classes) + ['disabled']
  445                     self.verbose_name = format_lazy(
  446                         '{verbose_name} {quota_exceeded}',
  447                         verbose_name=self.verbose_name,
  448                         quota_exceeded=_("(Quota exceeded)"))
  449             else:
  450                 self.verbose_name = _("Launch Instance")
  451                 classes = [c for c in self.classes if c != "disabled"]
  452                 self.classes = classes
  453         except Exception:
  454             LOG.exception("Failed to retrieve quota information")
  455             # If we can't get the quota information, leave it to the
  456             # API to check when launching
  457         return True  # The action should always be displayed
  458 
  459     def single(self, table, request, object_id=None):
  460         self.allowed(request, None)
  461         return HttpResponse(self.render(is_table_action=True))
  462 
  463 
  464 class LaunchLinkNG(LaunchLink):
  465     name = "launch-ng"
  466     url = "horizon:project:instances:index"
  467     ajax = False
  468     classes = ("btn-launch", )
  469 
  470     def get_default_attrs(self):
  471         url = urls.reverse(self.url)
  472         ngclick = "modal.openLaunchInstanceWizard(" \
  473             "{ successUrl: '%s' })" % url
  474         self.attrs.update({
  475             'ng-controller': 'LaunchInstanceModalController as modal',
  476             'ng-click': ngclick
  477         })
  478         return super().get_default_attrs()
  479 
  480     def get_link_url(self, datum=None):
  481         return "javascript:void(0);"
  482 
  483 
  484 class EditInstance(policy.PolicyTargetMixin, tables.LinkAction):
  485     name = "edit"
  486     verbose_name = _("Edit Instance")
  487     url = "horizon:project:instances:update"
  488     classes = ("ajax-modal",)
  489     icon = "pencil"
  490     policy_rules = (("compute", "os_compute_api:servers:update"),)
  491 
  492     def get_link_url(self, project):
  493         return self._get_link_url(project, 'instance_info')
  494 
  495     def _get_link_url(self, project, step_slug):
  496         base_url = urls.reverse(self.url, args=[project.id])
  497         next_url = self.table.get_full_url()
  498         params = {"step": step_slug,
  499                   update_instance.UpdateInstance.redirect_param_name: next_url}
  500         param = urlencode(params)
  501         return "?".join([base_url, param])
  502 
  503     def allowed(self, request, instance):
  504         return not is_deleting(instance)
  505 
  506 
  507 class EditInstanceSecurityGroups(EditInstance):
  508     name = "edit_secgroups"
  509     verbose_name = _("Edit Security Groups")
  510 
  511     def get_link_url(self, project):
  512         return self._get_link_url(project, 'update_security_groups')
  513 
  514     def allowed(self, request, instance=None):
  515         if not api.base.is_service_enabled(request, 'network'):
  516             return False
  517         return (instance.status in ACTIVE_STATES and
  518                 not is_deleting(instance) and
  519                 request.user.tenant_id == instance.tenant_id)
  520 
  521 
  522 class EditPortSecurityGroups(tables.LinkAction):
  523     name = "edit_port_secgroups"
  524     verbose_name = _("Edit Port Security Groups")
  525     policy_rules = (("network", "update_security_group"),)
  526     url = "horizon:project:instances:detail"
  527     icon = "pencil"
  528 
  529     def get_link_url(self, instance):
  530         base_url = urls.reverse(self.url, args=[instance.id])
  531         return '%s?tab=%s__%s' % (base_url, 'instance_details', 'interfaces')
  532 
  533 
  534 class CreateSnapshot(policy.PolicyTargetMixin, tables.LinkAction):
  535     name = "snapshot"
  536     verbose_name = _("Create Snapshot")
  537     url = "horizon:project:images:snapshots:create"
  538     classes = ("ajax-modal",)
  539     icon = "camera"
  540     policy_rules = (("compute", "os_compute_api:snapshot"),)
  541 
  542     def allowed(self, request, instance=None):
  543         return instance.status in SNAPSHOT_READY_STATES \
  544             and not is_deleting(instance)
  545 
  546 
  547 class ConsoleLink(policy.PolicyTargetMixin, tables.LinkAction):
  548     name = "console"
  549     verbose_name = _("Console")
  550     url = "horizon:project:instances:detail"
  551     classes = ("btn-console",)
  552     policy_rules = (("compute", "os_compute_api:os-consoles:index"),)
  553 
  554     def allowed(self, request, instance=None):
  555         # We check if ConsoleLink is allowed only if settings.CONSOLE_TYPE is
  556         # not set at all, or if it's set to any value other than None or False.
  557         return (bool(settings.CONSOLE_TYPE) and
  558                 instance.status in ACTIVE_STATES and
  559                 not is_deleting(instance))
  560 
  561     def get_link_url(self, datum):
  562         base_url = super().get_link_url(datum)
  563         tab_query_string = tabs.ConsoleTab(
  564             tabs.InstanceDetailTabs).get_query_string()
  565         return "?".join([base_url, tab_query_string])
  566 
  567 
  568 class LogLink(policy.PolicyTargetMixin, tables.LinkAction):
  569     name = "log"
  570     verbose_name = _("View Log")
  571     url = "horizon:project:instances:detail"
  572     classes = ("btn-log",)
  573     policy_rules = (("compute", "os_compute_api:os-console-output"),)
  574 
  575     def allowed(self, request, instance=None):
  576         return instance.status in ACTIVE_STATES and not is_deleting(instance)
  577 
  578     def get_link_url(self, datum):
  579         base_url = super().get_link_url(datum)
  580         tab_query_string = tabs.LogTab(
  581             tabs.InstanceDetailTabs).get_query_string()
  582         return "?".join([base_url, tab_query_string])
  583 
  584 
  585 class ResizeLink(policy.PolicyTargetMixin, tables.LinkAction):
  586     name = "resize"
  587     verbose_name = _("Resize Instance")
  588     url = "horizon:project:instances:resize"
  589     classes = ("ajax-modal", "btn-resize")
  590     policy_rules = (("compute", "os_compute_api:servers:resize"),)
  591     action_type = "danger"
  592 
  593     def get_link_url(self, project):
  594         return self._get_link_url(project, 'flavor_choice')
  595 
  596     def _get_link_url(self, project, step_slug):
  597         base_url = urls.reverse(self.url, args=[project.id])
  598         next_url = self.table.get_full_url()
  599         params = {"step": step_slug,
  600                   resize_instance.ResizeInstance.redirect_param_name: next_url}
  601         param = urlencode(params)
  602         return "?".join([base_url, param])
  603 
  604     def allowed(self, request, instance):
  605         return ((instance.status in ACTIVE_STATES or
  606                  instance.status == 'SHUTOFF') and
  607                 not is_deleting(instance))
  608 
  609 
  610 class ConfirmResize(policy.PolicyTargetMixin, tables.Action):
  611     name = "confirm"
  612     verbose_name = _("Confirm Resize/Migrate")
  613     classes = ("btn-confirm", "btn-action-required")
  614     policy_rules = (("compute", "os_compute_api:servers:confirm_resize"),)
  615 
  616     def allowed(self, request, instance):
  617         return instance.status == 'VERIFY_RESIZE'
  618 
  619     def single(self, table, request, obj_id):
  620         instance = table.get_object_by_id(obj_id)
  621         try:
  622             api.nova.server_confirm_resize(request, instance.id)
  623         except Exception:
  624             exceptions.handle(request,
  625                               _('Unable to confirm resize instance "%s".')
  626                               % (instance.name or instance.id))
  627         return shortcuts.redirect(request.get_full_path())
  628 
  629 
  630 class RevertResize(policy.PolicyTargetMixin, tables.Action):
  631     name = "revert"
  632     verbose_name = _("Revert Resize/Migrate")
  633     classes = ("btn-revert", "btn-action-required")
  634     policy_rules = (("compute", "os_compute_api:servers:revert_resize"),)
  635 
  636     def allowed(self, request, instance):
  637         return instance.status == 'VERIFY_RESIZE'
  638 
  639     def single(self, table, request, obj_id):
  640         instance = table.get_object_by_id(obj_id)
  641         try:
  642             api.nova.server_revert_resize(request, instance.id)
  643         except Exception:
  644             exceptions.handle(request,
  645                               _('Unable to revert resize instance "%s".')
  646                               % (instance.name or instance.id))
  647 
  648 
  649 class RebuildInstance(policy.PolicyTargetMixin, tables.LinkAction):
  650     name = "rebuild"
  651     verbose_name = _("Rebuild Instance")
  652     classes = ("btn-rebuild", "ajax-modal")
  653     url = "horizon:project:instances:rebuild"
  654     policy_rules = (("compute", "os_compute_api:servers:rebuild"),)
  655     action_type = "danger"
  656 
  657     def allowed(self, request, instance):
  658         return ((instance.status in ACTIVE_STATES or
  659                  instance.status == 'SHUTOFF') and
  660                 not is_deleting(instance))
  661 
  662     def get_link_url(self, datum):
  663         instance_id = self.table.get_object_id(datum)
  664         return urls.reverse(self.url, args=[instance_id])
  665 
  666 
  667 class DecryptInstancePassword(tables.LinkAction):
  668     name = "decryptpassword"
  669     verbose_name = _("Retrieve Password")
  670     classes = ("btn-decrypt", "ajax-modal")
  671     url = "horizon:project:instances:decryptpassword"
  672 
  673     def allowed(self, request, instance):
  674         return (settings.OPENSTACK_ENABLE_PASSWORD_RETRIEVE and
  675                 (instance.status in ACTIVE_STATES or
  676                  instance.status == 'SHUTOFF') and
  677                 not is_deleting(instance) and
  678                 get_keyname(instance) is not None)
  679 
  680     def get_link_url(self, datum):
  681         instance_id = self.table.get_object_id(datum)
  682         keypair_name = get_keyname(datum)
  683         return urls.reverse(self.url, args=[instance_id,
  684                                             keypair_name])
  685 
  686 
  687 class AssociateIP(policy.PolicyTargetMixin, tables.LinkAction):
  688     name = "associate"
  689     verbose_name = _("Associate Floating IP")
  690     url = "horizon:project:floating_ips:associate"
  691     classes = ("ajax-modal",)
  692     icon = "link"
  693     policy_rules = (("network", "update_floatingip"),)
  694 
  695     def allowed(self, request, instance):
  696         if not api.base.is_service_enabled(request, 'network'):
  697             return False
  698         if not api.neutron.floating_ip_supported(request):
  699             return False
  700         if api.neutron.floating_ip_simple_associate_supported(request):
  701             return False
  702         if instance.status == "ERROR":
  703             return False
  704         for addresses in instance.addresses.values():
  705             for address in addresses:
  706                 if address.get('OS-EXT-IPS:type') == "floating":
  707                     return False
  708         return not is_deleting(instance)
  709 
  710     def get_link_url(self, datum):
  711         base_url = urls.reverse(self.url)
  712         next_url = self.table.get_full_url()
  713         params = {
  714             "instance_id": self.table.get_object_id(datum),
  715             workflows.IPAssociationWorkflow.redirect_param_name: next_url}
  716         params = urlencode(params)
  717         return "?".join([base_url, params])
  718 
  719 
  720 class DisassociateIP(tables.LinkAction):
  721     name = "disassociate"
  722     verbose_name = _("Disassociate Floating IP")
  723     url = "horizon:project:instances:disassociate"
  724     classes = ("btn-disassociate", 'ajax-modal')
  725     policy_rules = (("network", "update_floatingip"),)
  726     action_type = "danger"
  727 
  728     def allowed(self, request, instance):
  729         if not api.base.is_service_enabled(request, 'network'):
  730             return False
  731         if not api.neutron.floating_ip_supported(request):
  732             return False
  733         for addresses in instance.addresses.values():
  734             for address in addresses:
  735                 if address.get('OS-EXT-IPS:type') == "floating":
  736                     return not is_deleting(instance)
  737         return False
  738 
  739 
  740 class UpdateMetadata(policy.PolicyTargetMixin, tables.LinkAction):
  741     name = "update_metadata"
  742     verbose_name = _("Update Metadata")
  743     ajax = False
  744     icon = "pencil"
  745     attrs = {"ng-controller": "MetadataModalHelperController as modal"}
  746     policy_rules = (("compute", "os_compute_api:server-metadata:update"),)
  747 
  748     def __init__(self, attrs=None, **kwargs):
  749         kwargs['preempt'] = True
  750         super().__init__(attrs, **kwargs)
  751 
  752     def get_link_url(self, datum):
  753         instance_id = self.table.get_object_id(datum)
  754         self.attrs['ng-click'] = (
  755             "modal.openMetadataModal('instance', '%s', true, 'metadata')"
  756             % instance_id)
  757         return "javascript:void(0);"
  758 
  759     def allowed(self, request, instance=None):
  760         return (instance and
  761                 instance.status.lower() != 'error')
  762 
  763 
  764 def instance_fault_to_friendly_message(instance):
  765     fault = getattr(instance, 'fault', {})
  766     message = fault.get('message', _("Unknown"))
  767     default_message = _("Please try again later [Error: %s].") % message
  768     fault_map = {
  769         'NoValidHost': _("There is not enough capacity for this "
  770                          "flavor in the selected availability zone. "
  771                          "Try again later or select a different availability "
  772                          "zone.")
  773     }
  774     return fault_map.get(message, default_message)
  775 
  776 
  777 def get_instance_error(instance):
  778     if instance.status.lower() != 'error':
  779         return None
  780     message = instance_fault_to_friendly_message(instance)
  781     preamble = _('Failed to perform requested operation on instance "%s", the '
  782                  'instance has an error status') % instance.name or instance.id
  783     message = format_lazy('{preamble}: {message}',
  784                           preamble=preamble, message=message)
  785     return message
  786 
  787 
  788 class UpdateRow(tables.Row):
  789     ajax = True
  790 
  791     def get_data(self, request, instance_id):
  792         instance = api.nova.server_get(request, instance_id)
  793         try:
  794             instance.full_flavor = instance_utils.resolve_flavor(request,
  795                                                                  instance)
  796         except Exception:
  797             exceptions.handle(request,
  798                               _('Unable to retrieve flavor information '
  799                                 'for instance "%s".') % instance_id,
  800                               ignore=True)
  801         try:
  802             api.network.servers_update_addresses(request, [instance])
  803         except Exception:
  804             exceptions.handle(request,
  805                               _('Unable to retrieve Network information '
  806                                 'for instance "%s".') % instance_id,
  807                               ignore=True)
  808         error = get_instance_error(instance)
  809         if error:
  810             messages.error(request, error)
  811         return instance
  812 
  813 
  814 class StartInstance(policy.PolicyTargetMixin, tables.BatchAction):
  815     name = "start"
  816     classes = ('btn-confirm',)
  817     policy_rules = (("compute", "os_compute_api:servers:start"),)
  818 
  819     @staticmethod
  820     def action_present(count):
  821         return ungettext_lazy(
  822             "Start Instance",
  823             "Start Instances",
  824             count
  825         )
  826 
  827     @staticmethod
  828     def action_past(count):
  829         return ungettext_lazy(
  830             "Started Instance",
  831             "Started Instances",
  832             count
  833         )
  834 
  835     def allowed(self, request, instance):
  836         return ((instance is None) or
  837                 (instance.status in ("SHUTDOWN", "SHUTOFF", "CRASHED")))
  838 
  839     def action(self, request, obj_id):
  840         api.nova.server_start(request, obj_id)
  841 
  842 
  843 class StopInstance(policy.PolicyTargetMixin, tables.BatchAction):
  844     name = "stop"
  845     policy_rules = (("compute", "os_compute_api:servers:stop"),)
  846     help_text = _("The instance(s) will be shut off.")
  847     action_type = "danger"
  848 
  849     @staticmethod
  850     def action_present(count):
  851         return npgettext_lazy(
  852             "Action to perform (the instance is currently running)",
  853             "Shut Off Instance",
  854             "Shut Off Instances",
  855             count
  856         )
  857 
  858     @staticmethod
  859     def action_past(count):
  860         return npgettext_lazy(
  861             "Past action (the instance is currently already Shut Off)",
  862             "Shut Off Instance",
  863             "Shut Off Instances",
  864             count
  865         )
  866 
  867     def allowed(self, request, instance):
  868         return (instance is None or
  869                 (get_power_state(instance) in ("RUNNING", "SUSPENDED") and
  870                  not is_deleting(instance)))
  871 
  872     def action(self, request, obj_id):
  873         api.nova.server_stop(request, obj_id)
  874 
  875 
  876 class LockInstance(policy.PolicyTargetMixin, tables.BatchAction):
  877     name = "lock"
  878     policy_rules = (("compute", "os_compute_api:os-lock-server:lock"),)
  879 
  880     @staticmethod
  881     def action_present(count):
  882         return ungettext_lazy(
  883             "Lock Instance",
  884             "Lock Instances",
  885             count
  886         )
  887 
  888     @staticmethod
  889     def action_past(count):
  890         return ungettext_lazy(
  891             "Locked Instance",
  892             "Locked Instances",
  893             count
  894         )
  895 
  896     # to only allow unlocked instances to be locked
  897     def allowed(self, request, instance):
  898         if getattr(instance, 'locked', False):
  899             return False
  900         if not api.nova.is_feature_available(request, "locked_attribute"):
  901             return False
  902         return True
  903 
  904     def action(self, request, obj_id):
  905         api.nova.server_lock(request, obj_id)
  906 
  907 
  908 class UnlockInstance(policy.PolicyTargetMixin, tables.BatchAction):
  909     name = "unlock"
  910     policy_rules = (("compute", "os_compute_api:os-lock-server:unlock"),)
  911 
  912     @staticmethod
  913     def action_present(count):
  914         return ungettext_lazy(
  915             "Unlock Instance",
  916             "Unlock Instances",
  917             count
  918         )
  919 
  920     @staticmethod
  921     def action_past(count):
  922         return ungettext_lazy(
  923             "Unlocked Instance",
  924             "Unlocked Instances",
  925             count
  926         )
  927 
  928     # to only allow locked instances to be unlocked
  929     def allowed(self, request, instance):
  930         if not getattr(instance, 'locked', True):
  931             return False
  932         if not api.nova.is_feature_available(request, "locked_attribute"):
  933             return False
  934         return True
  935 
  936     def action(self, request, obj_id):
  937         api.nova.server_unlock(request, obj_id)
  938 
  939 
  940 class AttachVolume(tables.LinkAction):
  941     name = "attach_volume"
  942     verbose_name = _("Attach Volume")
  943     url = "horizon:project:instances:attach_volume"
  944     classes = ("ajax-modal",)
  945     policy_rules = (
  946         ("compute", "os_compute_api:os-volumes-attachments:create"),)
  947 
  948     # This action should be disabled if the instance
  949     # is not active, or the instance is being deleted
  950     # or cinder is not enabled
  951     def allowed(self, request, instance=None):
  952         return (instance.status in ("ACTIVE") and
  953                 not is_deleting(instance) and
  954                 api.cinder.is_volume_service_enabled(request))
  955 
  956 
  957 class DetachVolume(AttachVolume):
  958     name = "detach_volume"
  959     verbose_name = _("Detach Volume")
  960     url = "horizon:project:instances:detach_volume"
  961     policy_rules = (
  962         ("compute", "os_compute_api:os-volumes-attachments:delete"),)
  963 
  964     # This action should be disabled if the instance
  965     # is not active, or the instance is being deleted
  966     # or cinder is not enabled
  967     def allowed(self, request, instance=None):
  968         return (instance.status in ("ACTIVE") and
  969                 not is_deleting(instance) and
  970                 api.cinder.is_volume_service_enabled(request))
  971 
  972 
  973 class AttachInterface(policy.PolicyTargetMixin, tables.LinkAction):
  974     name = "attach_interface"
  975     verbose_name = _("Attach Interface")
  976     classes = ("btn-confirm", "ajax-modal")
  977     url = "horizon:project:instances:attach_interface"
  978     policy_rules = (("compute", "os_compute_api:os-attach-interfaces"),)
  979 
  980     def allowed(self, request, instance):
  981         return ((instance.status in ACTIVE_STATES or
  982                  instance.status == 'SHUTOFF') and
  983                 not is_deleting(instance) and
  984                 api.base.is_service_enabled(request, 'network'))
  985 
  986     def get_link_url(self, datum):
  987         instance_id = self.table.get_object_id(datum)
  988         return urls.reverse(self.url, args=[instance_id])
  989 
  990 
  991 class DetachInterface(policy.PolicyTargetMixin, tables.LinkAction):
  992     name = "detach_interface"
  993     verbose_name = _("Detach Interface")
  994     classes = ("btn-confirm", "ajax-modal")
  995     url = "horizon:project:instances:detach_interface"
  996     policy_rules = (("compute", "os_compute_api:os-attach-interfaces:delete"),)
  997 
  998     def allowed(self, request, instance):
  999         if not api.base.is_service_enabled(request, 'network'):
 1000             return False
 1001         if is_deleting(instance):
 1002             return False
 1003         if (instance.status not in ACTIVE_STATES and
 1004                 instance.status != 'SHUTOFF'):
 1005             return False
 1006         for addresses in instance.addresses.values():
 1007             for address in addresses:
 1008                 if address.get('OS-EXT-IPS:type') == "fixed":
 1009                     return True
 1010         return False
 1011 
 1012     def get_link_url(self, datum):
 1013         instance_id = self.table.get_object_id(datum)
 1014         return urls.reverse(self.url, args=[instance_id])
 1015 
 1016 
 1017 def get_ips(instance):
 1018     template_name = 'project/instances/_instance_ips.html'
 1019     ip_groups = {}
 1020 
 1021     for ip_group, addresses in instance.addresses.items():
 1022         ips = [addr['addr'] for addr in addresses]
 1023         ips.sort(key=lambda ip: netaddr.IPAddress(ip).version)
 1024         ip_groups[ip_group] = ips
 1025 
 1026     context = {
 1027         "ip_groups": ip_groups,
 1028     }
 1029     return template.loader.render_to_string(template_name, context)
 1030 
 1031 
 1032 def get_flavor(instance):
 1033     if hasattr(instance, "full_flavor"):
 1034         template_name = 'project/instances/_instance_flavor.html'
 1035         size_ram = sizeformat.mb_float_format(instance.full_flavor.ram)
 1036         if instance.full_flavor.disk > 0:
 1037             size_disk = sizeformat.diskgbformat(instance.full_flavor.disk)
 1038         else:
 1039             size_disk = _("%s GB") % "0"
 1040         context = {
 1041             "name": instance.full_flavor.name,
 1042             "id": instance.id,
 1043             "size_disk": size_disk,
 1044             "size_ram": size_ram,
 1045             "vcpus": instance.full_flavor.vcpus,
 1046             "flavor_id": getattr(instance.full_flavor, 'id', None)
 1047         }
 1048         return template.loader.render_to_string(template_name, context)
 1049     return _("Not available")
 1050 
 1051 
 1052 def get_keyname(instance):
 1053     if hasattr(instance, "key_name"):
 1054         keyname = instance.key_name
 1055         return keyname
 1056     return _("Not available")
 1057 
 1058 
 1059 def get_power_state(instance):
 1060     return POWER_STATES.get(getattr(instance, "OS-EXT-STS:power_state", 0), '')
 1061 
 1062 
 1063 STATUS_DISPLAY_CHOICES = (
 1064     ("deleted", pgettext_lazy("Current status of an Instance", "Deleted")),
 1065     ("active", pgettext_lazy("Current status of an Instance", "Active")),
 1066     ("shutoff", pgettext_lazy("Current status of an Instance", "Shutoff")),
 1067     ("suspended", pgettext_lazy("Current status of an Instance",
 1068                                 "Suspended")),
 1069     ("paused", pgettext_lazy("Current status of an Instance", "Paused")),
 1070     ("error", pgettext_lazy("Current status of an Instance", "Error")),
 1071     ("resize", pgettext_lazy("Current status of an Instance",
 1072                              "Resize/Migrate")),
 1073     ("verify_resize", pgettext_lazy("Current status of an Instance",
 1074                                     "Confirm or Revert Resize/Migrate")),
 1075     ("revert_resize", pgettext_lazy(
 1076         "Current status of an Instance", "Revert Resize/Migrate")),
 1077     ("reboot", pgettext_lazy("Current status of an Instance", "Reboot")),
 1078     ("hard_reboot", pgettext_lazy("Current status of an Instance",
 1079                                   "Hard Reboot")),
 1080     ("password", pgettext_lazy("Current status of an Instance", "Password")),
 1081     ("rebuild", pgettext_lazy("Current status of an Instance", "Rebuild")),
 1082     ("migrating", pgettext_lazy("Current status of an Instance",
 1083                                 "Migrating")),
 1084     ("build", pgettext_lazy("Current status of an Instance", "Build")),
 1085     ("rescue", pgettext_lazy("Current status of an Instance", "Rescue")),
 1086     ("soft-delete", pgettext_lazy("Current status of an Instance",
 1087                                   "Soft Deleted")),
 1088     ("shelved", pgettext_lazy("Current status of an Instance", "Shelved")),
 1089     ("shelved_offloaded", pgettext_lazy("Current status of an Instance",
 1090                                         "Shelved Offloaded")),
 1091     # these vm states are used when generating CSV usage summary
 1092     ("building", pgettext_lazy("Current status of an Instance", "Building")),
 1093     ("stopped", pgettext_lazy("Current status of an Instance", "Stopped")),
 1094     ("rescued", pgettext_lazy("Current status of an Instance", "Rescued")),
 1095     ("resized", pgettext_lazy("Current status of an Instance", "Resized")),
 1096 )
 1097 
 1098 TASK_DISPLAY_NONE = pgettext_lazy("Task status of an Instance", "None")
 1099 
 1100 # Mapping of task states taken from Nova's nova/compute/task_states.py
 1101 TASK_DISPLAY_CHOICES = (
 1102     ("scheduling", pgettext_lazy("Task status of an Instance",
 1103                                  "Scheduling")),
 1104     ("block_device_mapping", pgettext_lazy("Task status of an Instance",
 1105                                            "Block Device Mapping")),
 1106     ("networking", pgettext_lazy("Task status of an Instance",
 1107                                  "Networking")),
 1108     ("spawning", pgettext_lazy("Task status of an Instance", "Spawning")),
 1109     ("image_snapshot", pgettext_lazy("Task status of an Instance",
 1110                                      "Snapshotting")),
 1111     ("image_snapshot_pending", pgettext_lazy("Task status of an Instance",
 1112                                              "Image Snapshot Pending")),
 1113     ("image_pending_upload", pgettext_lazy("Task status of an Instance",
 1114                                            "Image Pending Upload")),
 1115     ("image_uploading", pgettext_lazy("Task status of an Instance",
 1116                                       "Image Uploading")),
 1117     ("image_backup", pgettext_lazy("Task status of an Instance",
 1118                                    "Image Backup")),
 1119     ("updating_password", pgettext_lazy("Task status of an Instance",
 1120                                         "Updating Password")),
 1121     ("resize_prep", pgettext_lazy("Task status of an Instance",
 1122                                   "Preparing Resize or Migrate")),
 1123     ("resize_migrating", pgettext_lazy("Task status of an Instance",
 1124                                        "Resizing or Migrating")),
 1125     ("resize_migrated", pgettext_lazy("Task status of an Instance",
 1126                                       "Resized or Migrated")),
 1127     ("resize_finish", pgettext_lazy("Task status of an Instance",
 1128                                     "Finishing Resize or Migrate")),
 1129     ("resize_reverting", pgettext_lazy("Task status of an Instance",
 1130                                        "Reverting Resize or Migrate")),
 1131     ("resize_confirming", pgettext_lazy("Task status of an Instance",
 1132                                         "Confirming Resize or Migrate")),
 1133     ("rebooting", pgettext_lazy("Task status of an Instance", "Rebooting")),
 1134     ("reboot_pending", pgettext_lazy("Task status of an Instance",
 1135                                      "Reboot Pending")),
 1136     ("reboot_started", pgettext_lazy("Task status of an Instance",
 1137                                      "Reboot Started")),
 1138     ("rebooting_hard", pgettext_lazy("Task status of an Instance",
 1139                                      "Hard Rebooting")),
 1140     ("reboot_pending_hard", pgettext_lazy("Task status of an Instance",
 1141                                           "Hard Reboot Pending")),
 1142     ("reboot_started_hard", pgettext_lazy("Task status of an Instance",
 1143                                           "Hard Reboot Started")),
 1144     ("pausing", pgettext_lazy("Task status of an Instance", "Pausing")),
 1145     ("unpausing", pgettext_lazy("Task status of an Instance", "Resuming")),
 1146     ("suspending", pgettext_lazy("Task status of an Instance",
 1147                                  "Suspending")),
 1148     ("resuming", pgettext_lazy("Task status of an Instance", "Resuming")),
 1149     ("powering-off", pgettext_lazy("Task status of an Instance",
 1150                                    "Powering Off")),
 1151     ("powering-on", pgettext_lazy("Task status of an Instance",
 1152                                   "Powering On")),
 1153     ("rescuing", pgettext_lazy("Task status of an Instance", "Rescuing")),
 1154     ("unrescuing", pgettext_lazy("Task status of an Instance",
 1155                                  "Unrescuing")),
 1156     ("rebuilding", pgettext_lazy("Task status of an Instance",
 1157                                  "Rebuilding")),
 1158     ("rebuild_block_device_mapping", pgettext_lazy(
 1159         "Task status of an Instance", "Rebuild Block Device Mapping")),
 1160     ("rebuild_spawning", pgettext_lazy("Task status of an Instance",
 1161                                        "Rebuild Spawning")),
 1162     ("migrating", pgettext_lazy("Task status of an Instance", "Migrating")),
 1163     ("deleting", pgettext_lazy("Task status of an Instance", "Deleting")),
 1164     ("soft-deleting", pgettext_lazy("Task status of an Instance",
 1165                                     "Soft Deleting")),
 1166     ("restoring", pgettext_lazy("Task status of an Instance", "Restoring")),
 1167     ("shelving", pgettext_lazy("Task status of an Instance", "Shelving")),
 1168     ("shelving_image_pending_upload", pgettext_lazy(
 1169         "Task status of an Instance", "Shelving Image Pending Upload")),
 1170     ("shelving_image_uploading", pgettext_lazy("Task status of an Instance",
 1171                                                "Shelving Image Uploading")),
 1172     ("shelving_offloading", pgettext_lazy("Task status of an Instance",
 1173                                           "Shelving Offloading")),
 1174     ("unshelving", pgettext_lazy("Task status of an Instance",
 1175                                  "Unshelving")),
 1176 )
 1177 
 1178 POWER_DISPLAY_CHOICES = (
 1179     ("NO STATE", pgettext_lazy("Power state of an Instance", "No State")),
 1180     ("RUNNING", pgettext_lazy("Power state of an Instance", "Running")),
 1181     ("BLOCKED", pgettext_lazy("Power state of an Instance", "Blocked")),
 1182     ("PAUSED", pgettext_lazy("Power state of an Instance", "Paused")),
 1183     ("SHUTDOWN", pgettext_lazy("Power state of an Instance", "Shut Down")),
 1184     ("SHUTOFF", pgettext_lazy("Power state of an Instance", "Shut Off")),
 1185     ("CRASHED", pgettext_lazy("Power state of an Instance", "Crashed")),
 1186     ("SUSPENDED", pgettext_lazy("Power state of an Instance", "Suspended")),
 1187     ("FAILED", pgettext_lazy("Power state of an Instance", "Failed")),
 1188     ("BUILDING", pgettext_lazy("Power state of an Instance", "Building")),
 1189 )
 1190 
 1191 INSTANCE_FILTER_CHOICES = (
 1192     ('uuid', _("Instance ID ="), True),
 1193     ('name', _("Instance Name ="), True),
 1194     ('image', _("Image ID ="), True),
 1195     ('image_name', _("Image Name ="), True),
 1196     ('ip', _("IPv4 Address ="), True),
 1197     ('ip6', _("IPv6 Address ="), True, None,
 1198      api.neutron.is_enabled_by_config('enable_ipv6')),
 1199     ('flavor', _("Flavor ID ="), True),
 1200     ('flavor_name', _("Flavor Name ="), True),
 1201     ('key_name', _("Key Pair Name ="), True),
 1202     ('status', _("Status ="), True),
 1203     ('availability_zone', _("Availability Zone ="), True),
 1204     ('changes-since', _("Changes Since"), True,
 1205         _("Filter by an ISO 8061 formatted time, e.g. 2016-06-14T06:27:59Z")),
 1206     ('vcpus', _("vCPUs ="), True),
 1207 )
 1208 
 1209 
 1210 class InstancesFilterAction(tables.FilterAction):
 1211     filter_type = "server"
 1212     filter_choices = INSTANCE_FILTER_CHOICES
 1213 
 1214 
 1215 def render_locked(instance):
 1216     if not hasattr(instance, 'locked'):
 1217         return ""
 1218     if instance.locked:
 1219         icon_classes = "fa fa-fw fa-lock"
 1220         help_tooltip = _("This instance is currently locked. To enable more "
 1221                          "actions on it, please unlock it by selecting Unlock "
 1222                          "Instance from the actions menu.")
 1223     else:
 1224         icon_classes = "fa fa-fw fa-unlock text-muted"
 1225         help_tooltip = _("This instance is unlocked.")
 1226 
 1227     locked_status = ('<span data-toggle="tooltip" title="{}" class="{}">'
 1228                      '</span>').format(help_tooltip, icon_classes)
 1229     return mark_safe(locked_status)
 1230 
 1231 
 1232 def get_server_detail_link(obj, request):
 1233     return get_url_with_pagination(request,
 1234                                    InstancesTable._meta.pagination_param,
 1235                                    InstancesTable._meta.prev_pagination_param,
 1236                                    'horizon:project:instances:detail',
 1237                                    obj.id)
 1238 
 1239 
 1240 class InstancesTable(tables.DataTable):
 1241     TASK_STATUS_CHOICES = (
 1242         (None, True),
 1243         ("none", True)
 1244     )
 1245     STATUS_CHOICES = (
 1246         ("active", True),
 1247         ("shutoff", True),
 1248         ("suspended", True),
 1249         ("paused", True),
 1250         ("error", False),
 1251         ("rescue", True),
 1252         ("shelved", True),
 1253         ("shelved_offloaded", True),
 1254     )
 1255     name = tables.WrappingColumn("name",
 1256                                  link=get_server_detail_link,
 1257                                  verbose_name=_("Instance Name"))
 1258     image_name = tables.WrappingColumn("image_name",
 1259                                        verbose_name=_("Image Name"))
 1260     ip = tables.Column(get_ips,
 1261                        verbose_name=_("IP Address"),
 1262                        attrs={'data-type': "ip"})
 1263     flavor = tables.Column(get_flavor,
 1264                            sortable=False,
 1265                            verbose_name=_("Flavor"))
 1266     keypair = tables.Column(get_keyname, verbose_name=_("Key Pair"))
 1267     status = tables.Column("status",
 1268                            filters=(title, filters.replace_underscores),
 1269                            verbose_name=_("Status"),
 1270                            status=True,
 1271                            status_choices=STATUS_CHOICES,
 1272                            display_choices=STATUS_DISPLAY_CHOICES)
 1273     locked = tables.Column(render_locked,
 1274                            verbose_name="",
 1275                            sortable=False)
 1276     az = tables.Column("availability_zone",
 1277                        verbose_name=_("Availability Zone"))
 1278     task = tables.Column("OS-EXT-STS:task_state",
 1279                          verbose_name=_("Task"),
 1280                          empty_value=TASK_DISPLAY_NONE,
 1281                          status=True,
 1282                          status_choices=TASK_STATUS_CHOICES,
 1283                          display_choices=TASK_DISPLAY_CHOICES)
 1284     state = tables.Column(get_power_state,
 1285                           filters=(title, filters.replace_underscores),
 1286                           verbose_name=_("Power State"),
 1287                           display_choices=POWER_DISPLAY_CHOICES)
 1288     created = tables.Column("created",
 1289                             verbose_name=_("Age"),
 1290                             filters=(filters.parse_isotime,
 1291                                      filters.timesince_sortable),
 1292                             attrs={'data-type': 'timesince'})
 1293 
 1294     class Meta(object):
 1295         name = "instances"
 1296         verbose_name = _("Instances")
 1297         status_columns = ["status", "task"]
 1298         row_class = UpdateRow
 1299         table_actions_menu = (StartInstance, StopInstance, SoftRebootInstance)
 1300         launch_actions = ()
 1301         if settings.LAUNCH_INSTANCE_LEGACY_ENABLED:
 1302             launch_actions = (LaunchLink,) + launch_actions
 1303         if settings.LAUNCH_INSTANCE_NG_ENABLED:
 1304             launch_actions = (LaunchLinkNG,) + launch_actions
 1305         table_actions = launch_actions + (DeleteInstance,
 1306                                           InstancesFilterAction)
 1307         row_actions = (StartInstance, ConfirmResize, RevertResize,
 1308                        CreateSnapshot, AssociateIP, DisassociateIP,
 1309                        AttachInterface, DetachInterface, EditInstance,
 1310                        AttachVolume, DetachVolume,
 1311                        UpdateMetadata, DecryptInstancePassword,
 1312                        EditInstanceSecurityGroups,
 1313                        EditPortSecurityGroups,
 1314                        ConsoleLink, LogLink,
 1315                        RescueInstance, UnRescueInstance,
 1316                        TogglePause, ToggleSuspend, ToggleShelve,
 1317                        ResizeLink, LockInstance, UnlockInstance,
 1318                        SoftRebootInstance, RebootInstance,
 1319                        StopInstance, RebuildInstance, DeleteInstance)