Julie Pichon 2ed62bb2e5 Fix volume snapshot creation
The referenced template was wrong, and the quota usages information
had not been switched to using limits.

Thanks to Nuno Santos and Rohan Kanade for the initial versions of
this patch.

Change-Id: I97f578b84cd1cd4babab2580c60f0132026e3c93
Closes-Bug: #1194506
2013-09-18 18:19:19 +01:00

320 lines
14 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 # noqa
from django.core.urlresolvers import reverse # noqa
from django.forms import ValidationError # noqa
from django.template.defaultfilters import filesizeformat # noqa
from django.utils.translation import ugettext_lazy as _ # noqa
from horizon import exceptions
from horizon import forms
from horizon import messages
from horizon.utils import fields
from horizon.utils import functions
from horizon.utils.memoized import memoized # noqa
from openstack_dashboard import api
from openstack_dashboard.api import cinder
from openstack_dashboard.api import glance
from openstack_dashboard.dashboards.project.images_and_snapshots import utils
from openstack_dashboard.dashboards.project.instances import tables
from openstack_dashboard.usage import quotas
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)"))
volume_source_type = forms.ChoiceField(label=_("Volume Source"),
required=False)
snapshot_source = forms.ChoiceField(
label=_("Use snapshot as a source"),
widget=fields.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=fields.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]
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 Exception:
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 Exception:
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 = functions.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 Exception:
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 Exception:
exceptions.handle(request, _("Unable to retrieve "
"volume snapshots."))
images = utils.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 = functions.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:
usages = quotas.tenant_limit_usages(self.request)
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 = functions.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 = {}
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 Exception:
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 tables.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 Exception:
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 Exception:
redirect = reverse("horizon:project:images_and_snapshots:index")
exceptions.handle(request,
_('Unable to create volume snapshot.'),
redirect=redirect)