Merge "Add ability to create a volume from an image"
This commit is contained in:
commit
9685906e3f
@ -9,6 +9,16 @@ horizon.forms = {
|
||||
var $volSize = $form.find('input#id_size');
|
||||
$volSize.val($option.data("size"));
|
||||
});
|
||||
},
|
||||
handle_image_source: function() {
|
||||
$("div.table_wrapper, #modal_wrapper").on("change", "select#id_image_source", function(evt) {
|
||||
var $option = $(this).find("option:selected");
|
||||
var $form = $(this).closest('form');
|
||||
var $volName = $form.find('input#id_name');
|
||||
$volName.val($option.data("name"));
|
||||
var $volSize = $form.find('input#id_size');
|
||||
$volSize.val($option.data("size"));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -67,6 +77,7 @@ horizon.addInitFunction(function () {
|
||||
horizon.modals.addModalInitFunction(horizon.forms.init_examples);
|
||||
|
||||
horizon.forms.handle_snapshot_source();
|
||||
horizon.forms.handle_image_source();
|
||||
|
||||
// Bind event handlers to confirm dangerous actions.
|
||||
$("body").on("click", "form button.btn-danger", function (evt) {
|
||||
@ -108,6 +119,28 @@ horizon.addInitFunction(function () {
|
||||
$(modal).find('select.switchable').trigger('change');
|
||||
});
|
||||
|
||||
// Handle field toggles for the Create Volume source type field
|
||||
function update_volume_source_displayed_fields (field) {
|
||||
var $this = $(field),
|
||||
base_type = $this.val();
|
||||
|
||||
$this.find("option").each(function () {
|
||||
if (this.value != base_type) {
|
||||
$("#id_" + this.value).closest(".control-group").hide();
|
||||
} else {
|
||||
$("#id_" + this.value).closest(".control-group").show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on('change', '#id_volume_source_type', function (evt) {
|
||||
update_volume_source_displayed_fields(this);
|
||||
});
|
||||
|
||||
$('#id_volume_source_type').change();
|
||||
horizon.modals.addModalInitFunction(function (modal) {
|
||||
$(modal).find("#id_volume_source_type").change();
|
||||
});
|
||||
|
||||
/* Help tooltips */
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import math
|
||||
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.utils.functional import lazy
|
||||
|
||||
@ -7,3 +9,9 @@ def _lazy_join(separator, strings):
|
||||
for s in strings])
|
||||
|
||||
lazy_join = lazy(_lazy_join, unicode)
|
||||
|
||||
|
||||
def bytes_to_gigabytes(bytes):
|
||||
# Converts the number of bytes to the next highest number of Gigabytes
|
||||
# For example 5000000 (5 Meg) would return '1'
|
||||
return int(math.ceil(float(bytes) / 1024 ** 3))
|
||||
|
@ -89,10 +89,10 @@ def volume_get(request, volume_id):
|
||||
|
||||
|
||||
def volume_create(request, size, name, description, volume_type,
|
||||
snapshot_id=None, metadata=None):
|
||||
snapshot_id=None, metadata=None, image_id=None):
|
||||
return cinderclient(request).volumes.create(size, display_name=name,
|
||||
display_description=description, volume_type=volume_type,
|
||||
snapshot_id=snapshot_id, metadata=metadata)
|
||||
snapshot_id=snapshot_id, metadata=metadata, imageRef=image_id)
|
||||
|
||||
|
||||
def volume_delete(request, volume_id):
|
||||
|
@ -84,6 +84,23 @@ class EditImage(tables.LinkAction):
|
||||
return False
|
||||
|
||||
|
||||
class CreateVolumeFromImage(tables.LinkAction):
|
||||
name = "create_volume_from_image"
|
||||
verbose_name = _("Create Volume")
|
||||
url = "horizon:project:volumes:create"
|
||||
classes = ("ajax-modal", "btn-camera")
|
||||
|
||||
def get_link_url(self, datum):
|
||||
base_url = reverse(self.url)
|
||||
params = urlencode({"image_id": self.table.get_object_id(datum)})
|
||||
return "?".join([base_url, params])
|
||||
|
||||
def allowed(self, request, image=None):
|
||||
if image:
|
||||
return image.status == "active"
|
||||
return False
|
||||
|
||||
|
||||
def filter_tenants():
|
||||
return getattr(settings, 'IMAGES_LIST_FILTER_TENANTS', [])
|
||||
|
||||
@ -199,5 +216,6 @@ class ImagesTable(tables.DataTable):
|
||||
# all the columns by default.
|
||||
columns = ["name", "status", "public", "protected", "disk_format"]
|
||||
table_actions = (OwnerFilter, CreateImage, DeleteImage,)
|
||||
row_actions = (LaunchImage, EditImage, DeleteImage,)
|
||||
row_actions = (LaunchImage, CreateVolumeFromImage,
|
||||
EditImage, DeleteImage,)
|
||||
pagination_param = "image_marker"
|
||||
|
@ -0,0 +1,63 @@
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
|
||||
from openstack_dashboard.api import glance
|
||||
|
||||
|
||||
def get_available_images(request, project_id=None, images_cache=None):
|
||||
"""
|
||||
Returns a list of images that are public or owned by the given
|
||||
project_id. If project_id is not specified, only public images
|
||||
are returned.
|
||||
|
||||
:param images_cache:
|
||||
An optional dict-like object in which to
|
||||
cache public and per-project id image metadata.
|
||||
"""
|
||||
if images_cache is None:
|
||||
images_cache = {}
|
||||
public_images = images_cache.get('public_images', [])
|
||||
images_by_project = images_cache.get('images_by_project', {})
|
||||
if 'public_images' not in images_cache:
|
||||
public = {"is_public": True,
|
||||
"status": "active"}
|
||||
try:
|
||||
images, _more = glance.image_list_detailed(
|
||||
request, filters=public)
|
||||
[public_images.append(image) for image in images]
|
||||
images_cache['public_images'] = public_images
|
||||
except:
|
||||
exceptions.handle(request,
|
||||
_("Unable to retrieve public images."))
|
||||
|
||||
# Preempt if we don't have a project_id yet.
|
||||
if project_id is None:
|
||||
images_by_project[project_id] = []
|
||||
|
||||
if project_id not in images_by_project:
|
||||
owner = {"property-owner_id": project_id,
|
||||
"status": "active"}
|
||||
try:
|
||||
owned_images, _more = glance.image_list_detailed(
|
||||
request, filters=owner)
|
||||
except:
|
||||
exceptions.handle(request,
|
||||
_("Unable to retrieve images for "
|
||||
"the current project."))
|
||||
images_by_project[project_id] = owned_images
|
||||
if 'images_by_project' not in images_cache:
|
||||
images_cache['images_by_project'] = images_by_project
|
||||
|
||||
owned_images = images_by_project[project_id]
|
||||
images = owned_images + public_images
|
||||
|
||||
# Remove duplicate images
|
||||
image_ids = []
|
||||
final_images = []
|
||||
for image in images:
|
||||
if image.id not in image_ids:
|
||||
image_ids.append(image.id)
|
||||
final_images.append(image)
|
||||
return [image for image in final_images
|
||||
if image.container_format not in ('aki', 'ari')]
|
@ -30,8 +30,8 @@ from horizon import workflows
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api import cinder
|
||||
from openstack_dashboard.api import glance
|
||||
from openstack_dashboard.usage import quotas
|
||||
from ...images_and_snapshots.utils import get_available_images
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -219,52 +219,14 @@ class SetInstanceDetailsAction(workflows.Action):
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def _get_available_images(self, request, context):
|
||||
project_id = context.get('project_id', None)
|
||||
if not hasattr(self, "_public_images"):
|
||||
public = {"is_public": True,
|
||||
"status": "active"}
|
||||
try:
|
||||
public_images, _more = glance.image_list_detailed(
|
||||
request, filters=public)
|
||||
except:
|
||||
public_images = []
|
||||
exceptions.handle(request,
|
||||
_("Unable to retrieve public images."))
|
||||
self._public_images = public_images
|
||||
|
||||
# Preempt if we don't have a project_id yet.
|
||||
if project_id is None:
|
||||
setattr(self, "_images_for_%s" % project_id, [])
|
||||
|
||||
if not hasattr(self, "_images_for_%s" % project_id):
|
||||
owner = {"property-owner_id": project_id,
|
||||
"status": "active"}
|
||||
try:
|
||||
owned_images, _more = glance.image_list_detailed(
|
||||
request, filters=owner)
|
||||
except:
|
||||
owned_images = []
|
||||
exceptions.handle(request,
|
||||
_("Unable to retrieve images for "
|
||||
"the current project."))
|
||||
setattr(self, "_images_for_%s" % project_id, owned_images)
|
||||
|
||||
owned_images = getattr(self, "_images_for_%s" % project_id)
|
||||
images = owned_images + self._public_images
|
||||
|
||||
# Remove duplicate images
|
||||
image_ids = []
|
||||
final_images = []
|
||||
for image in images:
|
||||
if image.id not in image_ids:
|
||||
image_ids.append(image.id)
|
||||
final_images.append(image)
|
||||
return [image for image in final_images
|
||||
if image.container_format not in ('aki', 'ari')]
|
||||
def _init_images_cache(self):
|
||||
if not hasattr(self, '_images_cache'):
|
||||
self._images_cache = {}
|
||||
|
||||
def populate_image_id_choices(self, request, context):
|
||||
images = self._get_available_images(request, context)
|
||||
self._init_images_cache()
|
||||
images = get_available_images(request, context.get('project_id'),
|
||||
self._images_cache)
|
||||
choices = [(image.id, image.name)
|
||||
for image in images
|
||||
if image.properties.get("image_type", '') != "snapshot"]
|
||||
@ -275,7 +237,9 @@ class SetInstanceDetailsAction(workflows.Action):
|
||||
return choices
|
||||
|
||||
def populate_instance_snapshot_id_choices(self, request, context):
|
||||
images = self._get_available_images(request, context)
|
||||
self._init_images_cache()
|
||||
images = get_available_images(request, context.get('project_id'),
|
||||
self._images_cache)
|
||||
choices = [(image.id, image.name)
|
||||
for image in images
|
||||
if image.properties.get("image_type", '') == "snapshot"]
|
||||
|
@ -10,17 +10,21 @@ 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 forms
|
||||
from horizon import exceptions
|
||||
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 openstack_dashboard import api
|
||||
from openstack_dashboard.api import cinder
|
||||
from openstack_dashboard.api import glance
|
||||
from openstack_dashboard.usage import quotas
|
||||
from ..images_and_snapshots.utils import get_available_images
|
||||
from ..instances.tables import ACTIVE_STATES
|
||||
|
||||
|
||||
@ -32,6 +36,8 @@ class CreateForm(forms.SelfHandlingForm):
|
||||
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'},
|
||||
@ -40,6 +46,15 @@ class CreateForm(forms.SelfHandlingForm):
|
||||
("%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)
|
||||
@ -84,13 +99,35 @@ class CreateForm(forms.SelfHandlingForm):
|
||||
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
|
||||
@ -100,6 +137,27 @@ class CreateForm(forms.SelfHandlingForm):
|
||||
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
|
||||
@ -109,7 +167,10 @@ class CreateForm(forms.SelfHandlingForm):
|
||||
usages = quotas.tenant_quota_usages(request)
|
||||
|
||||
snapshot_id = None
|
||||
if (data.get("snapshot_source", 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"])
|
||||
@ -119,6 +180,18 @@ class CreateForm(forms.SelfHandlingForm):
|
||||
'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'])
|
||||
@ -146,6 +219,7 @@ class CreateForm(forms.SelfHandlingForm):
|
||||
data['description'],
|
||||
data['type'],
|
||||
snapshot_id=snapshot_id,
|
||||
image_id=image_id,
|
||||
metadata=metadata)
|
||||
message = 'Creating volume "%s"' % data['name']
|
||||
messages.info(request, message)
|
||||
@ -162,6 +236,10 @@ class CreateForm(forms.SelfHandlingForm):
|
||||
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"),
|
||||
|
@ -35,6 +35,7 @@ class VolumeViewTests(test.TestCase):
|
||||
@test.create_stubs({cinder: ('volume_create',
|
||||
'volume_snapshot_list',
|
||||
'volume_type_list',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def test_create_volume(self):
|
||||
volume = self.volumes.first()
|
||||
@ -52,13 +53,22 @@ class VolumeViewTests(test.TestCase):
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_snapshots.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'],
|
||||
formData['type'],
|
||||
metadata={},
|
||||
snapshot_id=None).AndReturn(volume)
|
||||
snapshot_id=None,
|
||||
image_id=None).AndReturn(volume)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
@ -70,6 +80,53 @@ class VolumeViewTests(test.TestCase):
|
||||
|
||||
@test.create_stubs({cinder: ('volume_create',
|
||||
'volume_snapshot_list',
|
||||
'volume_type_list',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def test_create_volume_dropdown(self):
|
||||
volume = self.volumes.first()
|
||||
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}}
|
||||
formData = {'name': u'A Volume I Am Making',
|
||||
'description': u'This is a volume I am making for a test.',
|
||||
'method': u'CreateForm',
|
||||
'size': 50,
|
||||
'type': '',
|
||||
'volume_source_type': 'no_source_type',
|
||||
'snapshot_source': self.volume_snapshots.first().id,
|
||||
'image_source': self.images.first().id}
|
||||
|
||||
cinder.volume_type_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_types.list())
|
||||
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_snapshots.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])
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
cinder.volume_create(IsA(http.HttpRequest),
|
||||
formData['size'],
|
||||
formData['name'],
|
||||
formData['description'],
|
||||
'',
|
||||
metadata={},
|
||||
snapshot_id=None,
|
||||
image_id=None).\
|
||||
AndReturn(volume)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:project:volumes:create')
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
redirect_url = reverse('horizon:project:volumes:index')
|
||||
self.assertRedirectsNoFollow(res, redirect_url)
|
||||
|
||||
@test.create_stubs({cinder: ('volume_create',
|
||||
'volume_snapshot_get',
|
||||
'volume_get',
|
||||
'volume_type_list',),
|
||||
@ -85,7 +142,6 @@ class VolumeViewTests(test.TestCase):
|
||||
'type': '',
|
||||
'snapshot_source': snapshot.id}
|
||||
|
||||
# first call- with url param
|
||||
cinder.volume_type_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_types.list())
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
@ -99,25 +155,9 @@ class VolumeViewTests(test.TestCase):
|
||||
formData['description'],
|
||||
'',
|
||||
metadata={},
|
||||
snapshot_id=snapshot.id).\
|
||||
snapshot_id=snapshot.id,
|
||||
image_id=None).\
|
||||
AndReturn(volume)
|
||||
# second call- with dropdown
|
||||
cinder.volume_type_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_types.list())
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_snapshots.list())
|
||||
cinder.volume_snapshot_get(IsA(http.HttpRequest),
|
||||
str(snapshot.id)).AndReturn(snapshot)
|
||||
cinder.volume_create(IsA(http.HttpRequest),
|
||||
formData['size'],
|
||||
formData['name'],
|
||||
formData['description'],
|
||||
'',
|
||||
metadata={},
|
||||
snapshot_id=snapshot.id).\
|
||||
AndReturn(volume)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# get snapshot from url
|
||||
@ -129,6 +169,52 @@ class VolumeViewTests(test.TestCase):
|
||||
redirect_url = reverse('horizon:project:volumes:index')
|
||||
self.assertRedirectsNoFollow(res, redirect_url)
|
||||
|
||||
@test.create_stubs({cinder: ('volume_create',
|
||||
'volume_snapshot_list',
|
||||
'volume_snapshot_get',
|
||||
'volume_get',
|
||||
'volume_type_list',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def test_create_volume_from_snapshot_dropdown(self):
|
||||
volume = self.volumes.first()
|
||||
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}}
|
||||
snapshot = self.volume_snapshots.first()
|
||||
formData = {'name': u'A Volume I Am Making',
|
||||
'description': u'This is a volume I am making for a test.',
|
||||
'method': u'CreateForm',
|
||||
'size': 50,
|
||||
'type': '',
|
||||
'volume_source_type': 'snapshot_source',
|
||||
'snapshot_source': snapshot.id}
|
||||
|
||||
cinder.volume_type_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_types.list())
|
||||
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_snapshots.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])
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
cinder.volume_snapshot_get(IsA(http.HttpRequest),
|
||||
str(snapshot.id)).AndReturn(snapshot)
|
||||
cinder.volume_create(IsA(http.HttpRequest),
|
||||
formData['size'],
|
||||
formData['name'],
|
||||
formData['description'],
|
||||
'',
|
||||
metadata={},
|
||||
snapshot_id=snapshot.id,
|
||||
image_id=None).\
|
||||
AndReturn(volume)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# get snapshot from dropdown list
|
||||
url = reverse('horizon:project:volumes:create')
|
||||
res = self.client.post(url, formData)
|
||||
@ -139,6 +225,7 @@ class VolumeViewTests(test.TestCase):
|
||||
@test.create_stubs({cinder: ('volume_snapshot_get',
|
||||
'volume_type_list',
|
||||
'volume_get',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def test_create_volume_from_snapshot_invalid_size(self):
|
||||
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}}
|
||||
@ -168,7 +255,132 @@ class VolumeViewTests(test.TestCase):
|
||||
"The volume size cannot be less than the "
|
||||
"snapshot size (40GB)")
|
||||
|
||||
@test.create_stubs({cinder: ('volume_create',
|
||||
'volume_type_list',),
|
||||
api.glance: ('image_get',),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def test_create_volume_from_image(self):
|
||||
volume = self.volumes.first()
|
||||
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}}
|
||||
image = self.images.first()
|
||||
formData = {'name': u'A Volume I Am Making',
|
||||
'description': u'This is a volume I am making for a test.',
|
||||
'method': u'CreateForm',
|
||||
'size': 40,
|
||||
'type': '',
|
||||
'image_source': image.id}
|
||||
|
||||
cinder.volume_type_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_types.list())
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
api.glance.image_get(IsA(http.HttpRequest),
|
||||
str(image.id)).AndReturn(image)
|
||||
cinder.volume_create(IsA(http.HttpRequest),
|
||||
formData['size'],
|
||||
formData['name'],
|
||||
formData['description'],
|
||||
'',
|
||||
metadata={},
|
||||
snapshot_id=None,
|
||||
image_id=image.id).\
|
||||
AndReturn(volume)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# get image from url
|
||||
url = reverse('horizon:project:volumes:create')
|
||||
res = self.client.post("?".join([url,
|
||||
"image_id=" + str(image.id)]),
|
||||
formData)
|
||||
|
||||
redirect_url = reverse('horizon:project:volumes:index')
|
||||
self.assertRedirectsNoFollow(res, redirect_url)
|
||||
|
||||
@test.create_stubs({cinder: ('volume_create',
|
||||
'volume_type_list',
|
||||
'volume_snapshot_list',),
|
||||
api.glance: ('image_get',
|
||||
'image_list_detailed'),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def test_create_volume_from_image_dropdown(self):
|
||||
volume = self.volumes.first()
|
||||
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}}
|
||||
image = self.images.first()
|
||||
formData = {'name': u'A Volume I Am Making',
|
||||
'description': u'This is a volume I am making for a test.',
|
||||
'method': u'CreateForm',
|
||||
'size': 30,
|
||||
'type': '',
|
||||
'volume_source_type': 'image_source',
|
||||
'snapshot_source': self.volume_snapshots.first().id,
|
||||
'image_source': image.id}
|
||||
|
||||
cinder.volume_type_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_types.list())
|
||||
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_snapshots.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])
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
api.glance.image_get(IsA(http.HttpRequest),
|
||||
str(image.id)).AndReturn(image)
|
||||
cinder.volume_create(IsA(http.HttpRequest),
|
||||
formData['size'],
|
||||
formData['name'],
|
||||
formData['description'],
|
||||
'',
|
||||
metadata={},
|
||||
snapshot_id=None,
|
||||
image_id=image.id).\
|
||||
AndReturn(volume)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# get image from dropdown list
|
||||
url = reverse('horizon:project:volumes:create')
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
redirect_url = reverse('horizon:project:volumes:index')
|
||||
self.assertRedirectsNoFollow(res, redirect_url)
|
||||
|
||||
@test.create_stubs({cinder: ('volume_type_list',),
|
||||
api.glance: ('image_get',
|
||||
'image_list_detailed'),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def test_create_volume_from_image_invalid_size(self):
|
||||
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}}
|
||||
image = self.images.first()
|
||||
formData = {'name': u'A Volume I Am Making',
|
||||
'description': u'This is a volume I am making for a test.',
|
||||
'method': u'CreateForm',
|
||||
'size': 1, 'image_source': image.id}
|
||||
|
||||
cinder.volume_type_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_types.list())
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
api.glance.image_get(IsA(http.HttpRequest),
|
||||
str(image.id)).AndReturn(image)
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:project:volumes:create')
|
||||
res = self.client.post("?".join([url,
|
||||
"image_id=" + str(image.id)]),
|
||||
formData, follow=True)
|
||||
self.assertEqual(res.redirect_chain, [])
|
||||
self.assertFormError(res, 'form', None,
|
||||
"The volume size cannot be less than the "
|
||||
"image size (20.0 GB)")
|
||||
|
||||
@test.create_stubs({cinder: ('volume_snapshot_list', 'volume_type_list',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def test_create_volume_gb_used_over_alloted_quota(self):
|
||||
usage = {'gigabytes': {'available': 100, 'used': 20}}
|
||||
@ -182,6 +394,14 @@ class VolumeViewTests(test.TestCase):
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_snapshots.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])
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
@ -194,6 +414,7 @@ class VolumeViewTests(test.TestCase):
|
||||
self.assertEqual(res.context['form'].errors['__all__'], expected_error)
|
||||
|
||||
@test.create_stubs({cinder: ('volume_snapshot_list', 'volume_type_list',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def test_create_volume_number_over_alloted_quota(self):
|
||||
usage = {'gigabytes': {'available': 100, 'used': 20},
|
||||
@ -208,6 +429,14 @@ class VolumeViewTests(test.TestCase):
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_snapshots.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])
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
@ -222,6 +451,7 @@ class VolumeViewTests(test.TestCase):
|
||||
@test.create_stubs({cinder: ('volume_create',
|
||||
'volume_snapshot_list',
|
||||
'volume_type_list',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def test_create_volume_encrypted(self):
|
||||
volume = self.volumes.first()
|
||||
@ -244,13 +474,22 @@ class VolumeViewTests(test.TestCase):
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_snapshots.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'],
|
||||
formData['type'],
|
||||
metadata={'encryption': formData['encryption']},
|
||||
snapshot_id=None).AndReturn(volume)
|
||||
snapshot_id=None,
|
||||
image_id=None).AndReturn(volume)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
@ -263,6 +502,7 @@ class VolumeViewTests(test.TestCase):
|
||||
settings.OPENSTACK_HYPERVISOR_FEATURES['can_encrypt_volumes'] = PREV
|
||||
|
||||
@test.create_stubs({cinder: ('volume_snapshot_list', 'volume_type_list',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def test_create_volume_cannot_encrypt(self):
|
||||
volume = self.volumes.first()
|
||||
@ -289,6 +529,14 @@ class VolumeViewTests(test.TestCase):
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
|
||||
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_snapshots.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])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
|
@ -57,6 +57,7 @@ def data(TEST):
|
||||
image_dict = {'id': '007e7d55-fe1e-4c5c-bf08-44b4a4964822',
|
||||
'name': 'public_image',
|
||||
'status': "active",
|
||||
'size': 20 * 1024 ** 3,
|
||||
'owner': TEST.tenant.id,
|
||||
'container_format': 'novaImage',
|
||||
'properties': {'image_type': u'image'},
|
||||
@ -67,6 +68,7 @@ def data(TEST):
|
||||
image_dict = {'id': 'a001c047-22f8-47d0-80a1-8ec94a9524fe',
|
||||
'name': 'private_image',
|
||||
'status': "active",
|
||||
'size': 10 * 1024 ** 2,
|
||||
'owner': TEST.tenant.id,
|
||||
'container_format': 'aki',
|
||||
'is_public': False,
|
||||
@ -77,6 +79,7 @@ def data(TEST):
|
||||
'name': 'protected_images',
|
||||
'status': "active",
|
||||
'owner': TEST.tenant.id,
|
||||
'size': 2 * 1024 ** 3,
|
||||
'container_format': 'novaImage',
|
||||
'properties': {'image_type': u'image'},
|
||||
'is_public': True,
|
||||
@ -86,6 +89,7 @@ def data(TEST):
|
||||
image_dict = {'id': '278905a6-4b52-4d1e-98f9-8c57bb25ba32',
|
||||
'name': 'public_image 2',
|
||||
'status': "active",
|
||||
'size': 5 * 1024 ** 3,
|
||||
'owner': TEST.tenant.id,
|
||||
'container_format': 'novaImage',
|
||||
'properties': {'image_type': u'image'},
|
||||
@ -96,6 +100,7 @@ def data(TEST):
|
||||
image_dict = {'id': '710a1acf-a3e3-41dd-a32d-5d6b6c86ea10',
|
||||
'name': 'private_image 2',
|
||||
'status': "active",
|
||||
'size': 30 * 1024 ** 3,
|
||||
'owner': TEST.tenant.id,
|
||||
'container_format': 'aki',
|
||||
'is_public': False,
|
||||
@ -105,6 +110,7 @@ def data(TEST):
|
||||
image_dict = {'id': '7cd892fd-5652-40f3-a450-547615680132',
|
||||
'name': 'private_image 3',
|
||||
'status': "active",
|
||||
'size': 2 * 1024 ** 3,
|
||||
'owner': TEST.tenant.id,
|
||||
'container_format': 'aki',
|
||||
'is_public': False,
|
||||
@ -115,6 +121,7 @@ def data(TEST):
|
||||
image_dict = {'id': 'c8756975-7a3b-4e43-b7f7-433576112849',
|
||||
'name': 'shared_image 1',
|
||||
'status': "active",
|
||||
'size': 8 * 1024 ** 3,
|
||||
'owner': 'someothertenant',
|
||||
'container_format': 'aki',
|
||||
'is_public': False,
|
||||
@ -126,6 +133,7 @@ def data(TEST):
|
||||
image_dict = {'id': 'f448704f-0ce5-4d34-8441-11b6581c6619',
|
||||
'name': 'official_image 1',
|
||||
'status': "active",
|
||||
'size': 2 * 1024 ** 3,
|
||||
'owner': 'officialtenant',
|
||||
'container_format': 'aki',
|
||||
'is_public': True,
|
||||
|
Loading…
x
Reference in New Issue
Block a user