Tihomir Trifonov 2392da498a Added action for creating a volume from snapshot
Added a new action to create new volume from
a volume snapshot. The snapshot_id is passed to
nova volume API. The 'Create Volume' form is
reused, with the 'size' value being checked against
the snapshot size, as it cannot be less than
the snapshot size.

Fixes bug 1023740

PATCH SET 4: Added dropdown to select snapshot source
in 'Create Volume' form, when loaded from /nova/volumes/
When a snapshot is selected from the dropdown, the name
and size are pre-filled in the form (I've removed
the description copy as it is probably specific
for the reason a snapshot is being created).

PATCH SET 6: rebased with master, fixed a bug with
get_context in nova/volume/views

Change-Id: I20dd8d698f5d8481938417557e09dcdc9b891e82
2012-07-24 13:06:17 +03:00

208 lines
9.2 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
# All rights reserved.
"""
Views for managing Nova volumes.
"""
from django.core.urlresolvers import reverse
from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import forms
from horizon import exceptions
from horizon import messages
from horizon.utils.fields import SelectWidget
from horizon.utils.memoized import memoized
from ..instances.tables import ACTIVE_STATES
class CreateForm(forms.SelfHandlingForm):
name = forms.CharField(max_length="255", label=_("Volume Name"))
description = forms.CharField(widget=forms.Textarea,
label=_("Description"), required=False)
size = forms.IntegerField(min_value=1, label=_("Size (GB)"))
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)
def __init__(self, request, *args, **kwargs):
super(CreateForm, self).__init__(request, *args, **kwargs)
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),)
self.fields['size'].help_text = _('Volume size must be equal '
'to or greater than the snapshot size (%sGB)'
% snapshot.size)
except:
exceptions.handle(request,
_('Unable to load the specified snapshot.'))
else:
try:
snapshots = api.volume_snapshot_list(request)
if snapshots:
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."))
def handle(self, request, data):
try:
# FIXME(johnp): Nova (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 Nova to try and create.
usages = api.tenant_quota_usages(request)
snapshot_id = None
if (data.get("snapshot_source", None)):
# 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)
else:
if type(data['size']) is str:
data['size'] = int(data['size'])
if usages['gigabytes']['available'] < 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': usages['gigabytes']['available']}
raise ValidationError(error_message % params)
elif usages['volumes']['available'] <= 0:
error_message = _('You are already using all of your available'
' volumes.')
raise ValidationError(error_message)
volume = api.volume_create(request,
data['size'],
data['name'],
data['description'],
snapshot_id=snapshot_id)
message = 'Creating volume "%s"' % data['name']
messages.info(request, message)
return volume
except ValidationError, e:
return self.api_error(e.messages[0])
except:
exceptions.handle(request, ignore=True)
return self.api_error(_("Unable to create volume."))
@memoized
def get_snapshot(self, request, id):
return api.nova.volume_snapshot_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"), initial="/dev/vdc")
def __init__(self, *args, **kwargs):
super(AttachForm, self).__init__(*args, **kwargs)
# 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:
api.volume_attach(request,
data['volume_id'],
data['instance'],
data['device'])
vol_name = api.volume_get(request, data['volume_id']).display_name
message = _('Attaching volume %(vol)s to instance '
'%(inst)s on %(dev)s.') % {"vol": vol_name,
"inst": instance_name,
"dev": data['device']}
messages.info(request, message)
return True
except:
redirect = reverse("horizon:nova: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 = api.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:nova:images_and_snapshots:index")
exceptions.handle(request,
_('Unable to create volume snapshot.'),
redirect=redirect)