hyphon-zh 8770b32dfa Resizing a server by means of changing its flavor
Sometime we need resize the server when it is launched, such as the number of vcpu, the size of memory and the size of disk.
We can achieve this by means of changing its flavor.

Change-Id: I1ab6b61f286e951b644a2e66383ac62c6a6f887e
Implements: blueprint resize-server
2013-06-04 15:52:27 +08:00

523 lines
18 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django import shortcuts
from django import template
from django.core import urlresolvers
from django.template.defaultfilters import title
from django.utils.http import urlencode
from django.utils.translation import string_concat, ugettext_lazy as _
from horizon.conf import HORIZON_CONFIG
from horizon import exceptions
from horizon import messages
from horizon import tables
from horizon.templatetags import sizeformat
from horizon.utils.filters import replace_underscores
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.access_and_security \
.floating_ips.workflows import IPAssociationWorkflow
from .tabs import InstanceDetailTabs, LogTab, ConsoleTab
LOG = logging.getLogger(__name__)
ACTIVE_STATES = ("ACTIVE",)
SNAPSHOT_READY_STATES = ("ACTIVE", "SHUTOFF")
POWER_STATES = {
0: "NO STATE",
1: "RUNNING",
2: "BLOCKED",
3: "PAUSED",
4: "SHUTDOWN",
5: "SHUTOFF",
6: "CRASHED",
7: "SUSPENDED",
8: "FAILED",
9: "BUILDING",
}
PAUSE = 0
UNPAUSE = 1
SUSPEND = 0
RESUME = 1
def is_deleting(instance):
task_state = getattr(instance, "OS-EXT-STS:task_state", None)
if not task_state:
return False
return task_state.lower() == "deleting"
class TerminateInstance(tables.BatchAction):
name = "terminate"
action_present = _("Terminate")
action_past = _("Scheduled termination of")
data_type_singular = _("Instance")
data_type_plural = _("Instances")
classes = ('btn-danger', 'btn-terminate')
def allowed(self, request, instance=None):
return True
def action(self, request, obj_id):
api.nova.server_delete(request, obj_id)
class RebootInstance(tables.BatchAction):
name = "reboot"
action_present = _("Hard Reboot")
action_past = _("Hard Rebooted")
data_type_singular = _("Instance")
data_type_plural = _("Instances")
classes = ('btn-danger', 'btn-reboot')
def allowed(self, request, instance=None):
return ((instance.status in ACTIVE_STATES
or instance.status == 'SHUTOFF')
and not is_deleting(instance))
def action(self, request, obj_id):
api.nova.server_reboot(request, obj_id, api.nova.REBOOT_HARD)
class SoftRebootInstance(RebootInstance):
name = "soft_reboot"
action_present = _("Soft Reboot")
action_past = _("Soft Rebooted")
def action(self, request, obj_id):
api.nova.server_reboot(request, obj_id, api.nova.REBOOT_SOFT)
class TogglePause(tables.BatchAction):
name = "pause"
action_present = (_("Pause"), _("Resume"))
action_past = (_("Paused"), _("Resumed"))
data_type_singular = _("Instance")
data_type_plural = _("Instances")
classes = ("btn-pause",)
def allowed(self, request, instance=None):
self.paused = False
if not instance:
return self.paused
self.paused = instance.status == "PAUSED"
if self.paused:
self.current_present_action = UNPAUSE
else:
self.current_present_action = PAUSE
return ((instance.status in ACTIVE_STATES or self.paused)
and not is_deleting(instance))
def action(self, request, obj_id):
if self.paused:
api.nova.server_unpause(request, obj_id)
self.current_past_action = UNPAUSE
else:
api.nova.server_pause(request, obj_id)
self.current_past_action = PAUSE
class ToggleSuspend(tables.BatchAction):
name = "suspend"
action_present = (_("Suspend"), _("Resume"))
action_past = (_("Suspended"), _("Resumed"))
data_type_singular = _("Instance")
data_type_plural = _("Instances")
classes = ("btn-suspend",)
def allowed(self, request, instance=None):
self.suspended = False
if not instance:
self.suspended
self.suspended = instance.status == "SUSPENDED"
if self.suspended:
self.current_present_action = RESUME
else:
self.current_present_action = SUSPEND
return ((instance.status in ACTIVE_STATES or self.suspended)
and not is_deleting(instance))
def action(self, request, obj_id):
if self.suspended:
api.nova.server_resume(request, obj_id)
self.current_past_action = RESUME
else:
api.nova.server_suspend(request, obj_id)
self.current_past_action = SUSPEND
class LaunchLink(tables.LinkAction):
name = "launch"
verbose_name = _("Launch Instance")
url = "horizon:project:instances:launch"
classes = ("btn-launch", "ajax-modal")
def allowed(self, request, datum):
try:
limits = api.nova.tenant_absolute_limits(request, reserved=True)
instances_available = limits['maxTotalInstances'] \
- limits['totalInstancesUsed']
cores_available = limits['maxTotalCores'] \
- limits['totalCoresUsed']
ram_available = limits['maxTotalRAMSize'] - limits['totalRAMUsed']
if instances_available <= 0 or cores_available <= 0 \
or ram_available <= 0:
if "disabled" not in self.classes:
self.classes = [c for c in self.classes] + ['disabled']
self.verbose_name = string_concat(self.verbose_name, ' ',
_("(Quota exceeded)"))
else:
self.verbose_name = _("Launch Instance")
classes = [c for c in self.classes if c != "disabled"]
self.classes = classes
except:
LOG.exception("Failed to retrieve quota information")
# If we can't get the quota information, leave it to the
# API to check when launching
return True # The action should always be displayed
class EditInstance(tables.LinkAction):
name = "edit"
verbose_name = _("Edit Instance")
url = "horizon:project:instances:update"
classes = ("ajax-modal", "btn-edit")
def get_link_url(self, project):
return self._get_link_url(project, 'instance_info')
def _get_link_url(self, project, step_slug):
base_url = urlresolvers.reverse(self.url, args=[project.id])
param = urlencode({"step": step_slug})
return "?".join([base_url, param])
def allowed(self, request, instance):
return not is_deleting(instance)
class EditInstanceSecurityGroups(EditInstance):
name = "edit_secgroups"
verbose_name = _("Edit Security Groups")
def get_link_url(self, project):
return self._get_link_url(project, 'update_security_groups')
def allowed(self, request, instance=None):
return (instance.status in ACTIVE_STATES and
not is_deleting(instance) and
request.user.tenant_id == instance.tenant_id)
class CreateSnapshot(tables.LinkAction):
name = "snapshot"
verbose_name = _("Create Snapshot")
url = "horizon:project:images_and_snapshots:snapshots:create"
classes = ("ajax-modal", "btn-camera")
def allowed(self, request, instance=None):
return instance.status in SNAPSHOT_READY_STATES \
and not is_deleting(instance)
class ConsoleLink(tables.LinkAction):
name = "console"
verbose_name = _("Console")
url = "horizon:project:instances:detail"
classes = ("btn-console",)
def allowed(self, request, instance=None):
return instance.status in ACTIVE_STATES and not is_deleting(instance)
def get_link_url(self, datum):
base_url = super(ConsoleLink, self).get_link_url(datum)
tab_query_string = ConsoleTab(InstanceDetailTabs).get_query_string()
return "?".join([base_url, tab_query_string])
class LogLink(tables.LinkAction):
name = "log"
verbose_name = _("View Log")
url = "horizon:project:instances:detail"
classes = ("btn-log",)
def allowed(self, request, instance=None):
return instance.status in ACTIVE_STATES and not is_deleting(instance)
def get_link_url(self, datum):
base_url = super(LogLink, self).get_link_url(datum)
tab_query_string = LogTab(InstanceDetailTabs).get_query_string()
return "?".join([base_url, tab_query_string])
class ResizeLink(tables.LinkAction):
name = "resize"
verbose_name = _("Resize Instance")
url = "horizon:project:instances:resize"
classes = ("ajax-modal", "btn-resize")
def get_link_url(self, project):
return self._get_link_url(project, 'flavor_choice')
def _get_link_url(self, project, step_slug):
base_url = urlresolvers.reverse(self.url, args=[project.id])
param = urlencode({"step": step_slug})
return "?".join([base_url, param])
def allowed(self, request, instance):
return ((instance.status in ACTIVE_STATES
or instance.status == 'SHUTOFF')
and not is_deleting(instance))
class ConfirmResize(tables.Action):
name = "confirm"
verbose_name = _("Confirm Resize/Migrate")
classes = ("btn-confirm", "btn-action-required")
def allowed(self, request, instance):
return instance.status == 'VERIFY_RESIZE'
def single(self, table, request, instance):
api.nova.server_confirm_resize(request, instance)
class RevertResize(tables.Action):
name = "revert"
verbose_name = _("Revert Resize/Migrate")
classes = ("btn-revert", "btn-action-required")
def allowed(self, request, instance):
return instance.status == 'VERIFY_RESIZE'
def single(self, table, request, instance):
api.nova.server_revert_resize(request, instance)
class AssociateIP(tables.LinkAction):
name = "associate"
verbose_name = _("Associate Floating IP")
url = "horizon:project:access_and_security:floating_ips:associate"
classes = ("ajax-modal", "btn-associate")
def allowed(self, request, instance):
fip = api.network.NetworkClient(request).floating_ips
if fip.is_simple_associate_supported():
return False
return not is_deleting(instance)
def get_link_url(self, datum):
base_url = urlresolvers.reverse(self.url)
next = urlresolvers.reverse("horizon:project:instances:index")
params = {"instance_id": self.table.get_object_id(datum),
IPAssociationWorkflow.redirect_param_name: next}
params = urlencode(params)
return "?".join([base_url, params])
class SimpleAssociateIP(tables.Action):
name = "associate-simple"
verbose_name = _("Associate Floating IP")
classes = ("btn-associate-simple",)
def allowed(self, request, instance):
fip = api.network.NetworkClient(request).floating_ips
if not fip.is_simple_associate_supported():
return False
return not is_deleting(instance)
def single(self, table, request, instance_id):
try:
# target_id is port_id for Quantum and instance_id for Nova Network
# (Quantum API wrapper returns a 'portid_fixedip' string)
target_id = api.network.floating_ip_target_get_by_instance(
request, instance_id).split('_')[0]
fip = api.network.tenant_floating_ip_allocate(request)
api.network.floating_ip_associate(request, fip.id, target_id)
messages.success(request,
_("Successfully associated floating IP: %s")
% fip.ip)
except:
exceptions.handle(request,
_("Unable to associate floating IP."))
return shortcuts.redirect("horizon:project:instances:index")
class SimpleDisassociateIP(tables.Action):
name = "disassociate"
verbose_name = _("Disassociate Floating IP")
classes = ("btn-danger", "btn-disassociate",)
def allowed(self, request, instance):
if not HORIZON_CONFIG["simple_ip_management"]:
return False
return not is_deleting(instance)
def single(self, table, request, instance_id):
try:
# target_id is port_id for Quantum and instance_id for Nova Network
# (Quantum API wrapper returns a 'portid_fixedip' string)
target_id = api.network.floating_ip_target_get_by_instance(
request, instance_id).split('_')[0]
fips = [fip for fip in api.network.tenant_floating_ip_list(request)
if fip.port_id == target_id]
# Removing multiple floating IPs at once doesn't work, so this pops
# off the first one.
if fips:
fip = fips.pop()
api.network.floating_ip_disassociate(request,
fip.id, target_id)
api.network.tenant_floating_ip_release(request, fip.id)
messages.success(request,
_("Successfully disassociated "
"floating IP: %s") % fip.ip)
else:
messages.info(request, _("No floating IPs to disassociate."))
except:
exceptions.handle(request,
_("Unable to disassociate floating IP."))
return shortcuts.redirect("horizon:project:instances:index")
class UpdateRow(tables.Row):
ajax = True
def get_data(self, request, instance_id):
instance = api.nova.server_get(request, instance_id)
instance.full_flavor = api.nova.flavor_get(request,
instance.flavor["id"])
return instance
def get_ips(instance):
template_name = 'project/instances/_instance_ips.html'
context = {"instance": instance}
return template.loader.render_to_string(template_name, context)
def get_size(instance):
if hasattr(instance, "full_flavor"):
size_string = _("%(name)s | %(RAM)s RAM | %(VCPU)s VCPU "
"| %(disk)s Disk")
vals = {'name': instance.full_flavor.name,
'RAM': sizeformat.mbformat(instance.full_flavor.ram),
'VCPU': instance.full_flavor.vcpus,
'disk': sizeformat.diskgbformat(instance.full_flavor.disk)}
return size_string % vals
return _("Not available")
def get_keyname(instance):
if hasattr(instance, "key_name"):
keyname = instance.key_name
return keyname
return _("Not available")
def get_power_state(instance):
return POWER_STATES.get(getattr(instance, "OS-EXT-STS:power_state", 0), '')
STATUS_DISPLAY_CHOICES = (
("resize", "Resize/Migrate"),
("verify_resize", "Confirm or Revert Resize/Migrate"),
("revert_resize", "Revert Resize/Migrate"),
)
TASK_DISPLAY_CHOICES = (
("image_snapshot", "Snapshotting"),
("resize_prep", "Preparing Resize or Migrate"),
("resize_migrating", "Resizing or Migrating"),
("resize_migrated", "Resized or Migrated"),
("resize_finish", "Finishing Resize or Migrate"),
("resize_confirming", "Confirming Resize or Nigrate"),
("resize_reverting", "Reverting Resize or Migrate"),
("unpausing", "Resuming"),
)
class InstancesFilterAction(tables.FilterAction):
def filter(self, table, instances, filter_string):
""" Naive case-insensitive search. """
q = filter_string.lower()
return [instance for instance in instances
if q in instance.name.lower()]
class InstancesTable(tables.DataTable):
TASK_STATUS_CHOICES = (
(None, True),
("none", True)
)
STATUS_CHOICES = (
("active", True),
("shutoff", True),
("suspended", True),
("paused", True),
("error", False),
)
name = tables.Column("name",
link=("horizon:project:instances:detail"),
verbose_name=_("Instance Name"))
ip = tables.Column(get_ips,
verbose_name=_("IP Address"),
attrs={'data-type': "ip"})
size = tables.Column(get_size,
verbose_name=_("Size"),
attrs={'data-type': 'size'})
keypair = tables.Column(get_keyname, verbose_name=_("Keypair"))
status = tables.Column("status",
filters=(title, replace_underscores),
verbose_name=_("Status"),
status=True,
status_choices=STATUS_CHOICES,
display_choices=STATUS_DISPLAY_CHOICES)
task = tables.Column("OS-EXT-STS:task_state",
verbose_name=_("Task"),
filters=(title, replace_underscores),
status=True,
status_choices=TASK_STATUS_CHOICES,
display_choices=TASK_DISPLAY_CHOICES)
state = tables.Column(get_power_state,
filters=(title, replace_underscores),
verbose_name=_("Power State"))
class Meta:
name = "instances"
verbose_name = _("Instances")
status_columns = ["status", "task"]
row_class = UpdateRow
table_actions = (LaunchLink, TerminateInstance, InstancesFilterAction)
row_actions = (ConfirmResize, RevertResize, CreateSnapshot,
SimpleAssociateIP, AssociateIP,
SimpleDisassociateIP, EditInstance,
EditInstanceSecurityGroups, ConsoleLink, LogLink,
TogglePause, ToggleSuspend, ResizeLink,
SoftRebootInstance, RebootInstance, TerminateInstance)