Merge "Add ability to create a volume from an image"

This commit is contained in:
Jenkins 2013-05-14 21:27:44 +00:00 committed by Gerrit Code Review
commit 9685906e3f
9 changed files with 491 additions and 71 deletions

View File

@ -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 */

View File

@ -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))

View File

@ -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):

View File

@ -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"

View File

@ -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')]

View File

@ -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"]

View File

@ -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"),

View File

@ -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()

View File

@ -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,