
Python 3.x removed the "except x,y:" construct. Use "except x as y:" instead which works with all Python versions >= 2.6. Change-Id: Iedb4c5a6d8580cbb6c9697933d006e94b1db916d
352 lines
16 KiB
Python
352 lines
16 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2012 Nebula, Inc.
|
|
# All rights reserved.
|
|
|
|
"""
|
|
Views for managing volumes.
|
|
"""
|
|
|
|
from django.conf import settings
|
|
from django.core.urlresolvers import reverse
|
|
from django.forms import ValidationError
|
|
from django.template.defaultfilters import filesizeformat
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
from horizon import exceptions
|
|
from horizon import forms
|
|
from horizon import messages
|
|
from horizon.utils.fields import SelectWidget
|
|
from horizon.utils.functions import bytes_to_gigabytes
|
|
from horizon.utils.memoized import memoized
|
|
|
|
from ..images_and_snapshots.utils import get_available_images
|
|
from ..instances.tables import ACTIVE_STATES
|
|
from openstack_dashboard import api
|
|
from openstack_dashboard.api import cinder
|
|
from openstack_dashboard.api import glance
|
|
|
|
|
|
class CreateForm(forms.SelfHandlingForm):
|
|
name = forms.CharField(max_length="255", label=_("Volume Name"))
|
|
description = forms.CharField(widget=forms.Textarea,
|
|
label=_("Description"), required=False)
|
|
type = forms.ChoiceField(label=_("Type"),
|
|
required=False)
|
|
size = forms.IntegerField(min_value=1, label=_("Size (GB)"))
|
|
encryption = forms.ChoiceField(label=_("Encryption"), required=False)
|
|
volume_source_type = forms.ChoiceField(label=_("Volume Source"),
|
|
required=False)
|
|
snapshot_source = forms.ChoiceField(label=_("Use snapshot as a source"),
|
|
widget=SelectWidget(
|
|
attrs={'class': 'snapshot-selector'},
|
|
data_attrs=('size', 'display_name'),
|
|
transform=lambda x:
|
|
("%s (%sGB)" % (x.display_name,
|
|
x.size))),
|
|
required=False)
|
|
image_source = forms.ChoiceField(label=_("Use image as a source"),
|
|
widget=SelectWidget(
|
|
attrs={'class': 'image-selector'},
|
|
data_attrs=('size', 'name'),
|
|
transform=lambda x:
|
|
("%s (%s)" %
|
|
(x.name,
|
|
filesizeformat(x.bytes)))),
|
|
required=False)
|
|
|
|
def __init__(self, request, *args, **kwargs):
|
|
super(CreateForm, self).__init__(request, *args, **kwargs)
|
|
volume_types = cinder.volume_type_list(request)
|
|
self.fields['type'].choices = [("", "")] + \
|
|
[(type.name, type.name)
|
|
for type in volume_types]
|
|
|
|
# Hide the volume encryption field if the hypervisor doesn't support it
|
|
# NOTE: as of Grizzly this is not yet supported in Nova so enabling
|
|
# this setting will not do anything useful
|
|
hypervisor_features = getattr(settings,
|
|
"OPENSTACK_HYPERVISOR_FEATURES",
|
|
{})
|
|
can_encrypt_volumes = hypervisor_features.get("can_encrypt_volumes",
|
|
False)
|
|
|
|
if can_encrypt_volumes:
|
|
# TODO(laura-glendenning) get from api call in future
|
|
encryption_options = {"LUKS": "dmcrypt LUKS"}
|
|
self.fields['encryption'].choices = [("", "")] + \
|
|
[(enc, display) for enc, display in encryption_options.items()]
|
|
else:
|
|
self.fields['encryption'].widget = forms.widgets.HiddenInput()
|
|
self.fields['encryption'].required = False
|
|
|
|
if ("snapshot_id" in request.GET):
|
|
try:
|
|
snapshot = self.get_snapshot(request,
|
|
request.GET["snapshot_id"])
|
|
self.fields['name'].initial = snapshot.display_name
|
|
self.fields['size'].initial = snapshot.size
|
|
self.fields['snapshot_source'].choices = ((snapshot.id,
|
|
snapshot),)
|
|
try:
|
|
# Set the volume type from the original volume
|
|
orig_volume = cinder.volume_get(request,
|
|
snapshot.volume_id)
|
|
self.fields['type'].initial = orig_volume.volume_type
|
|
except:
|
|
pass
|
|
self.fields['size'].help_text = _('Volume size must be equal '
|
|
'to or greater than the snapshot size (%sGB)'
|
|
% snapshot.size)
|
|
del self.fields['image_source']
|
|
del self.fields['volume_source_type']
|
|
except:
|
|
exceptions.handle(request,
|
|
_('Unable to load the specified snapshot.'))
|
|
elif ('image_id' in request.GET):
|
|
try:
|
|
image = self.get_image(request,
|
|
request.GET["image_id"])
|
|
image.bytes = image.size
|
|
self.fields['name'].initial = image.name
|
|
self.fields['size'].initial = bytes_to_gigabytes(image.size)
|
|
self.fields['image_source'].choices = ((image.id, image),)
|
|
self.fields['size'].help_text = _('Volume size must be equal '
|
|
'to or greater than the image size (%s)'
|
|
% filesizeformat(image.size))
|
|
del self.fields['snapshot_source']
|
|
del self.fields['volume_source_type']
|
|
except:
|
|
msg = _('Unable to load the specified image. %s')
|
|
exceptions.handle(request, msg % request.GET['image_id'])
|
|
else:
|
|
source_type_choices = []
|
|
|
|
try:
|
|
snapshots = cinder.volume_snapshot_list(request)
|
|
if snapshots:
|
|
source_type_choices.append(("snapshot_source",
|
|
_("Snapshot")))
|
|
choices = [('', _("Choose a snapshot"))] + \
|
|
[(s.id, s) for s in snapshots]
|
|
self.fields['snapshot_source'].choices = choices
|
|
else:
|
|
del self.fields['snapshot_source']
|
|
except:
|
|
exceptions.handle(request, _("Unable to retrieve "
|
|
"volume snapshots."))
|
|
|
|
images = get_available_images(request,
|
|
request.user.tenant_id)
|
|
if images:
|
|
source_type_choices.append(("image_source", _("Image")))
|
|
choices = [('', _("Choose an image"))]
|
|
for image in images:
|
|
image.bytes = image.size
|
|
image.size = bytes_to_gigabytes(image.bytes)
|
|
choices.append((image.id, image))
|
|
self.fields['image_source'].choices = choices
|
|
else:
|
|
del self.fields['image_source']
|
|
|
|
if source_type_choices:
|
|
choices = ([('no_source_type',
|
|
_("No source, empty volume."))] +
|
|
source_type_choices)
|
|
self.fields['volume_source_type'].choices = choices
|
|
else:
|
|
del self.fields['volume_source_type']
|
|
|
|
def handle(self, request, data):
|
|
try:
|
|
# FIXME(johnp): cinderclient currently returns a useless
|
|
# error message when the quota is exceeded when trying to create
|
|
# a volume, so we need to check for that scenario here before we
|
|
# send it off to try and create.
|
|
usages = cinder.tenant_absolute_limits(self.request)
|
|
volumes = cinder.volume_list(self.request)
|
|
total_size = sum([getattr(volume, 'size', 0) for volume
|
|
in volumes])
|
|
usages['gigabytesUsed'] = total_size
|
|
usages['volumesUsed'] = len(volumes)
|
|
availableGB = usages['maxTotalVolumeGigabytes'] -\
|
|
usages['gigabytesUsed']
|
|
availableVol = usages['maxTotalVolumes'] - usages['volumesUsed']
|
|
|
|
snapshot_id = None
|
|
image_id = None
|
|
source_type = data.get('volume_source_type', None)
|
|
if (data.get("snapshot_source", None) and
|
|
source_type in [None, 'snapshot_source']):
|
|
# Create from Snapshot
|
|
snapshot = self.get_snapshot(request,
|
|
data["snapshot_source"])
|
|
snapshot_id = snapshot.id
|
|
if (data['size'] < snapshot.size):
|
|
error_message = _('The volume size cannot be less than '
|
|
'the snapshot size (%sGB)' %
|
|
snapshot.size)
|
|
raise ValidationError(error_message)
|
|
elif (data.get("image_source", None) and
|
|
source_type in [None, 'image_source']):
|
|
# Create from Snapshot
|
|
image = self.get_image(request,
|
|
data["image_source"])
|
|
image_id = image.id
|
|
image_size = bytes_to_gigabytes(image.size)
|
|
if (data['size'] < image_size):
|
|
error_message = _('The volume size cannot be less than '
|
|
'the image size (%s)' %
|
|
filesizeformat(image.size))
|
|
raise ValidationError(error_message)
|
|
else:
|
|
if type(data['size']) is str:
|
|
data['size'] = int(data['size'])
|
|
|
|
if availableGB < data['size']:
|
|
error_message = _('A volume of %(req)iGB cannot be created as '
|
|
'you only have %(avail)iGB of your quota '
|
|
'available.')
|
|
params = {'req': data['size'],
|
|
'avail': availableGB}
|
|
raise ValidationError(error_message % params)
|
|
elif availableVol <= 0:
|
|
error_message = _('You are already using all of your available'
|
|
' volumes.')
|
|
raise ValidationError(error_message)
|
|
|
|
metadata = {}
|
|
|
|
if data['encryption']:
|
|
metadata['encryption'] = data['encryption']
|
|
|
|
volume = cinder.volume_create(request,
|
|
data['size'],
|
|
data['name'],
|
|
data['description'],
|
|
data['type'],
|
|
snapshot_id=snapshot_id,
|
|
image_id=image_id,
|
|
metadata=metadata)
|
|
message = _('Creating volume "%s"') % data['name']
|
|
messages.info(request, message)
|
|
return volume
|
|
except ValidationError as e:
|
|
self.api_error(e.messages[0])
|
|
return False
|
|
except:
|
|
exceptions.handle(request, ignore=True)
|
|
self.api_error(_("Unable to create volume."))
|
|
return False
|
|
|
|
@memoized
|
|
def get_snapshot(self, request, id):
|
|
return cinder.volume_snapshot_get(request, id)
|
|
|
|
@memoized
|
|
def get_image(self, request, id):
|
|
return glance.image_get(request, id)
|
|
|
|
|
|
class AttachForm(forms.SelfHandlingForm):
|
|
instance = forms.ChoiceField(label=_("Attach to Instance"),
|
|
help_text=_("Select an instance to "
|
|
"attach to."))
|
|
device = forms.CharField(label=_("Device Name"))
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(AttachForm, self).__init__(*args, **kwargs)
|
|
|
|
# Hide the device field if the hypervisor doesn't support it.
|
|
hypervisor_features = getattr(settings,
|
|
"OPENSTACK_HYPERVISOR_FEATURES",
|
|
{})
|
|
can_set_mount_point = hypervisor_features.get("can_set_mount_point",
|
|
True)
|
|
if not can_set_mount_point:
|
|
self.fields['device'].widget = forms.widgets.HiddenInput()
|
|
self.fields['device'].required = False
|
|
|
|
# populate volume_id
|
|
volume = kwargs.get('initial', {}).get("volume", None)
|
|
if volume:
|
|
volume_id = volume.id
|
|
else:
|
|
volume_id = None
|
|
self.fields['volume_id'] = forms.CharField(widget=forms.HiddenInput(),
|
|
initial=volume_id)
|
|
|
|
# Populate instance choices
|
|
instance_list = kwargs.get('initial', {}).get('instances', [])
|
|
instances = []
|
|
for instance in instance_list:
|
|
if instance.status in ACTIVE_STATES and \
|
|
not any(instance.id == att["server_id"]
|
|
for att in volume.attachments):
|
|
instances.append((instance.id, '%s (%s)' % (instance.name,
|
|
instance.id)))
|
|
if instances:
|
|
instances.insert(0, ("", _("Select an instance")))
|
|
else:
|
|
instances = (("", _("No instances available")),)
|
|
self.fields['instance'].choices = instances
|
|
|
|
def handle(self, request, data):
|
|
instance_choices = dict(self.fields['instance'].choices)
|
|
instance_name = instance_choices.get(data['instance'],
|
|
_("Unknown instance (None)"))
|
|
# The name of the instance in the choices list has the ID appended to
|
|
# it, so let's slice that off...
|
|
instance_name = instance_name.rsplit(" (")[0]
|
|
try:
|
|
attach = api.nova.instance_volume_attach(request,
|
|
data['volume_id'],
|
|
data['instance'],
|
|
data.get('device', ''))
|
|
volume = cinder.volume_get(request, data['volume_id'])
|
|
if not volume.display_name:
|
|
volume_name = volume.id
|
|
else:
|
|
volume_name = volume.display_name
|
|
message = _('Attaching volume %(vol)s to instance '
|
|
'%(inst)s on %(dev)s.') % {"vol": volume_name,
|
|
"inst": instance_name,
|
|
"dev": attach.device}
|
|
messages.info(request, message)
|
|
return True
|
|
except:
|
|
redirect = reverse("horizon:project:volumes:index")
|
|
exceptions.handle(request,
|
|
_('Unable to attach volume.'),
|
|
redirect=redirect)
|
|
|
|
|
|
class CreateSnapshotForm(forms.SelfHandlingForm):
|
|
name = forms.CharField(max_length="255", label=_("Snapshot Name"))
|
|
description = forms.CharField(widget=forms.Textarea,
|
|
label=_("Description"), required=False)
|
|
|
|
def __init__(self, request, *args, **kwargs):
|
|
super(CreateSnapshotForm, self).__init__(request, *args, **kwargs)
|
|
|
|
# populate volume_id
|
|
volume_id = kwargs.get('initial', {}).get('volume_id', [])
|
|
self.fields['volume_id'] = forms.CharField(widget=forms.HiddenInput(),
|
|
initial=volume_id)
|
|
|
|
def handle(self, request, data):
|
|
try:
|
|
snapshot = cinder.volume_snapshot_create(request,
|
|
data['volume_id'],
|
|
data['name'],
|
|
data['description'])
|
|
|
|
message = _('Creating volume snapshot "%s"') % data['name']
|
|
messages.info(request, message)
|
|
return snapshot
|
|
except:
|
|
redirect = reverse("horizon:project:images_and_snapshots:index")
|
|
exceptions.handle(request,
|
|
_('Unable to create volume snapshot.'),
|
|
redirect=redirect)
|