Merge "Add create volume from volume"
This commit is contained in:
commit
080e868096
|
@ -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();
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue