Merge "Add create volume from volume"

This commit is contained in:
Jenkins 2014-02-06 13:21:21 +00:00 committed by Gerrit Code Review
commit 080e868096
4 changed files with 193 additions and 13 deletions

View File

@ -5,7 +5,24 @@ horizon.forms = {
var $option = $(this).find("option:selected");
var $form = $(this).closest('form');
var $volName = $form.find('input#id_name');
if ($volName.is(':empty')) {
if ($volName.val() == "") {
$volName.val($option.data("display_name"));
}
var $volSize = $form.find('input#id_size');
var volSize = parseInt($volSize.val(), 10) || -1;
var dataSize = parseInt($option.data("size"), 10) || -1;
if (volSize < dataSize) {
$volSize.val(dataSize);
}
});
},
handle_volume_source: function() {
$("div.table_wrapper, #modal_wrapper").on("change", "select#id_volume_source", function(evt) {
var $option = $(this).find("option:selected");
var $form = $(this).closest('form');
var $volName = $form.find('input#id_name');
if ($volName.val() == "") {
$volName.val($option.data("display_name"));
}
var $volSize = $form.find('input#id_size');
@ -22,7 +39,7 @@ horizon.forms = {
var $option = $(this).find("option:selected");
var $form = $(this).closest('form');
var $volName = $form.find('input#id_name');
if ($volName.is(':empty')) {
if ($volName.val() == "") {
$volName.val($option.data("name"));
}
var $volSize = $form.find('input#id_size');
@ -114,6 +131,7 @@ horizon.addInitFunction(function () {
horizon.modals.addModalInitFunction(horizon.forms.init_examples);
horizon.forms.handle_snapshot_source();
horizon.forms.handle_volume_source();
horizon.forms.handle_image_source();
horizon.forms.datepicker();

View File

@ -94,11 +94,11 @@ def volume_get(request, volume_id):
def volume_create(request, size, name, description, volume_type,
snapshot_id=None, metadata=None, image_id=None,
availability_zone=None):
availability_zone=None, source_volid=None):
return cinderclient(request).volumes.create(size, display_name=name,
display_description=description, volume_type=volume_type,
snapshot_id=snapshot_id, metadata=metadata, imageRef=image_id,
availability_zone=availability_zone)
availability_zone=availability_zone, source_volid=source_volid)
def volume_extend(request, volume_id, new_size):

View File

@ -66,6 +66,14 @@ class CreateForm(forms.SelfHandlingForm):
data_attrs=('size', 'name', 'min_disk'),
transform=lambda x: "%s (%s)" % (x.name, filesizeformat(x.bytes))),
required=False)
volume_source = forms.ChoiceField(
label=_("Use a volume as source"),
widget=fields.SelectWidget(
attrs={'class': 'image-selector'},
data_attrs=('size', 'display_name'),
transform=lambda x: "%s (%s)" % (x.display_name,
filesizeformat(x.size * 1024 * 1024 * 1024))),
required=False)
availability_zone = forms.ChoiceField(
label=_("Availability Zone"),
required=False,
@ -82,7 +90,7 @@ class CreateForm(forms.SelfHandlingForm):
[(type.name, type.name)
for type in volume_types]
if ("snapshot_id" in request.GET):
if "snapshot_id" in request.GET:
try:
snapshot = self.get_snapshot(request,
request.GET["snapshot_id"])
@ -106,7 +114,7 @@ class CreateForm(forms.SelfHandlingForm):
except Exception:
exceptions.handle(request,
_('Unable to load the specified snapshot.'))
elif ('image_id' in request.GET):
elif 'image_id' in request.GET:
self.fields['availability_zone'].choices = \
self.availability_zones(request)
try:
@ -134,6 +142,30 @@ class CreateForm(forms.SelfHandlingForm):
except Exception:
msg = _('Unable to load the specified image. %s')
exceptions.handle(request, msg % request.GET['image_id'])
elif 'volume_id' in request.GET:
self.fields['availability_zone'].choices = \
self.availability_zones(request)
volume = None
try:
volume = self.get_volume(request, request.GET["volume_id"])
except Exception:
msg = _('Unable to load the specified volume. %s')
exceptions.handle(request, msg % request.GET['volume_id'])
if volume is not None:
self.fields['name'].initial = volume.display_name
self.fields['description'].initial = volume.display_description
min_vol_size = volume.size
size_help_text = _('Volume size must be equal to or greater '
'than the origin volume size (%s)') \
% filesizeformat(volume.size)
self.fields['size'].initial = min_vol_size
self.fields['size'].help_text = size_help_text
self.fields['volume_source'].choices = ((volume.id, volume),)
self.fields['type'].initial = volume.type
del self.fields['snapshot_source']
del self.fields['image_source']
del self.fields['volume_source_type']
else:
source_type_choices = []
self.fields['availability_zone'].choices = \
@ -168,6 +200,16 @@ class CreateForm(forms.SelfHandlingForm):
else:
del self.fields['image_source']
volumes = self.get_volumes(request)
if volumes:
source_type_choices.append(("volume_source", _("Volume")))
choices = [('', _("Choose a volume"))]
for volume in volumes:
choices.append((volume.id, volume))
self.fields['volume_source'].choices = choices
else:
del self.fields['volume_source']
if source_type_choices:
choices = ([('no_source_type',
_("No source, empty volume"))] +
@ -204,6 +246,18 @@ class CreateForm(forms.SelfHandlingForm):
return zone_list
def get_volumes(self, request):
volumes = []
try:
volume_list = cinder.volume_list(self.request)
if volume_list is not None:
volumes = [v for v in volume_list
if v.status == api.cinder.VOLUME_STATE_AVAILABLE]
except Exception:
exceptions.handle(request,
_('Unable to retrieve list of volumes.'))
return volumes
def handle(self, request, data):
try:
usages = quotas.tenant_limit_usages(self.request)
@ -213,6 +267,7 @@ class CreateForm(forms.SelfHandlingForm):
snapshot_id = None
image_id = None
volume_id = None
source_type = data.get('volume_source_type', None)
az = data.get('availability_zone', None) or None
if (data.get("snapshot_source", None) and
@ -242,6 +297,16 @@ class CreateForm(forms.SelfHandlingForm):
error_message = _('The volume size cannot be less than '
'the image minimum disk size (%sGB)') % min_disk_size
raise ValidationError(error_message)
elif (data.get("volume_source", None) and
source_type in [None, 'volume_source']):
# Create from volume
volume = self.get_volume(request, data["volume_source"])
volume_id = volume.id
if data['size'] < volume.size:
error_message = _('The volume size cannot be less than '
'the volume size (%sGB)') % volume.size
raise ValidationError(error_message)
else:
if type(data['size']) is str:
data['size'] = int(data['size'])
@ -268,7 +333,8 @@ class CreateForm(forms.SelfHandlingForm):
snapshot_id=snapshot_id,
image_id=image_id,
metadata=metadata,
availability_zone=az)
availability_zone=az,
source_volid=volume_id)
message = _('Creating volume "%s"') % data['name']
messages.info(request, message)
return volume
@ -288,6 +354,10 @@ class CreateForm(forms.SelfHandlingForm):
def get_image(self, request, id):
return glance.image_get(request, id)
@memoized
def get_volume(self, request, id):
return cinder.volume_get(request, id)
class AttachForm(forms.SelfHandlingForm):
instance = forms.ChoiceField(label=_("Attach to Instance"),

View File

@ -39,6 +39,7 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_create',
'volume_snapshot_list',
'volume_type_list',
'volume_list',
'availability_zone_list',
'extension_supported'),
api.glance: ('image_list_detailed',),
@ -78,6 +79,8 @@ class VolumeViewTests(test.TestCase):
cinder.extension_supported(IsA(http.HttpRequest), 'AvailabilityZones')\
.AndReturn(True)
cinder.volume_list(IsA(
http.HttpRequest)).AndReturn(self.volumes.list())
cinder.volume_create(IsA(http.HttpRequest),
formData['size'],
@ -87,7 +90,8 @@ class VolumeViewTests(test.TestCase):
metadata={},
snapshot_id=None,
image_id=None,
availability_zone=formData['availability_zone'])\
availability_zone=formData['availability_zone'],
source_volid=None)\
.AndReturn(volume)
self.mox.ReplayAll()
@ -101,6 +105,7 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_create',
'volume_snapshot_list',
'volume_type_list',
'volume_list',
'availability_zone_list',
'extension_supported'),
api.glance: ('image_list_detailed',),
@ -132,6 +137,8 @@ class VolumeViewTests(test.TestCase):
filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \
.AndReturn([[], False])
cinder.volume_list(IsA(
http.HttpRequest)).AndReturn(self.volumes.list())
quotas.tenant_limit_usages(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
@ -148,7 +155,8 @@ class VolumeViewTests(test.TestCase):
metadata={},
snapshot_id=None,
image_id=None,
availability_zone=None).AndReturn(volume)
availability_zone=None,
source_volid=None).AndReturn(volume)
self.mox.ReplayAll()
@ -194,7 +202,8 @@ class VolumeViewTests(test.TestCase):
metadata={},
snapshot_id=snapshot.id,
image_id=None,
availability_zone=None).AndReturn(volume)
availability_zone=None,
source_volid=None).AndReturn(volume)
self.mox.ReplayAll()
# get snapshot from url
@ -206,10 +215,79 @@ class VolumeViewTests(test.TestCase):
redirect_url = reverse('horizon:project:volumes:index')
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({cinder: ('volume_create',
'volume_get',
'volume_list',
'volume_type_list',
'availability_zone_list',
'volume_snapshot_get',
'volume_snapshot_list',
'extension_supported'),
api.glance: ('image_list_detailed',),
quotas: ('tenant_limit_usages',)})
def test_create_volume_from_volume(self):
volume = self.volumes.first()
usage_limit = {'maxTotalVolumeGigabytes': 250,
'gigabytesUsed': 20,
'volumesUsed': len(self.volumes.list()),
'maxTotalVolumes': 6}
formData = {'name': u'A copy of a volume',
'description': u'This is a volume I am making for a test.',
'method': u'CreateForm',
'size': 50,
'type': '',
'volume_source_type': 'volume_source',
'volume_source': volume.id}
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_snapshots.list())
quotas.tenant_limit_usages(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_get(IsA(http.HttpRequest),
volume.id).AndReturn(self.volumes.first())
cinder.extension_supported(IsA(http.HttpRequest),
'AvailabilityZones').AndReturn(True)
cinder.availability_zone_list(IsA(http.HttpRequest)).AndReturn(
self.cinder_availability_zones.list())
api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'is_public': True,
'status': 'active'}) \
.AndReturn([self.images.list(), False])
api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \
.AndReturn([[], False])
cinder.volume_create(IsA(http.HttpRequest),
formData['size'],
formData['name'],
formData['description'],
'',
metadata={},
snapshot_id=None,
image_id=None,
availability_zone=None,
source_volid=volume.id).AndReturn(volume)
self.mox.ReplayAll()
url = reverse('horizon:project:volumes:create')
redirect_url = reverse('horizon:project:volumes:index')
res = self.client.post(url, formData)
self.assertNoFormErrors(res)
self.assertMessageCount(info=1)
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({cinder: ('volume_create',
'volume_snapshot_list',
'volume_snapshot_get',
'volume_get',
'volume_list',
'volume_type_list',
'availability_zone_list',
'extension_supported'),
@ -242,6 +320,8 @@ class VolumeViewTests(test.TestCase):
filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \
.AndReturn([[], False])
cinder.volume_list(IsA(
http.HttpRequest)).AndReturn(self.volumes.list())
quotas.tenant_limit_usages(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_snapshot_get(IsA(http.HttpRequest),
@ -260,7 +340,8 @@ class VolumeViewTests(test.TestCase):
metadata={},
snapshot_id=snapshot.id,
image_id=None,
availability_zone=None).AndReturn(volume)
availability_zone=None,
source_volid=None).AndReturn(volume)
self.mox.ReplayAll()
@ -350,7 +431,8 @@ class VolumeViewTests(test.TestCase):
metadata={},
snapshot_id=None,
image_id=image.id,
availability_zone=None).AndReturn(volume)
availability_zone=None,
source_volid=None).AndReturn(volume)
self.mox.ReplayAll()
@ -365,6 +447,7 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_create',
'volume_type_list',
'volume_list',
'volume_snapshot_list',
'availability_zone_list',
'extension_supported'),
@ -399,6 +482,8 @@ class VolumeViewTests(test.TestCase):
filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \
.AndReturn([[], False])
cinder.volume_list(IsA(
http.HttpRequest)).AndReturn(self.volumes.list())
quotas.tenant_limit_usages(IsA(http.HttpRequest)) \
.AndReturn(usage_limit)
api.glance.image_get(IsA(http.HttpRequest),
@ -417,7 +502,8 @@ class VolumeViewTests(test.TestCase):
metadata={},
snapshot_id=None,
image_id=image.id,
availability_zone=None).AndReturn(volume)
availability_zone=None,
source_volid=None).AndReturn(volume)
self.mox.ReplayAll()
@ -513,6 +599,7 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_snapshot_list',
'volume_type_list',
'volume_list',
'availability_zone_list',
'extension_supported'),
api.glance: ('image_list_detailed',),
@ -541,6 +628,8 @@ class VolumeViewTests(test.TestCase):
filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \
.AndReturn([[], False])
cinder.volume_list(IsA(
http.HttpRequest)).AndReturn(self.volumes.list())
cinder.extension_supported(IsA(http.HttpRequest), 'AvailabilityZones')\
.AndReturn(True)
cinder.availability_zone_list(IsA(http.HttpRequest)).AndReturn(
@ -559,6 +648,7 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_snapshot_list',
'volume_type_list',
'volume_list',
'availability_zone_list',
'extension_supported'),
api.glance: ('image_list_detailed',),
@ -587,6 +677,8 @@ class VolumeViewTests(test.TestCase):
filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \
.AndReturn([[], False])
cinder.volume_list(IsA(
http.HttpRequest)).AndReturn(self.volumes.list())
cinder.extension_supported(IsA(http.HttpRequest), 'AvailabilityZones')\
.AndReturn(True)
cinder.availability_zone_list(IsA(http.HttpRequest)).AndReturn(