Merge "Add Availability Zone to Volume screens"

This commit is contained in:
Jenkins 2013-10-16 10:41:20 +00:00 committed by Gerrit Code Review
commit 27b5b9f3fc
5 changed files with 186 additions and 25 deletions

View File

@ -28,8 +28,10 @@ from django.conf import settings # noqa
from django.utils.translation import ugettext_lazy as _ # noqa
from cinderclient.v1 import client as cinder_client
from cinderclient.v1.contrib import list_extensions as cinder_list_extensions
from horizon import exceptions
from horizon.utils.memoized import memoized # noqa
from openstack_dashboard.api import base
from openstack_dashboard.api import nova
@ -92,10 +94,12 @@ def volume_get(request, volume_id):
def volume_create(request, size, name, description, volume_type,
snapshot_id=None, metadata=None, image_id=None):
snapshot_id=None, metadata=None, image_id=None,
availability_zone=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)
snapshot_id=snapshot_id, metadata=metadata, imageRef=image_id,
availability_zone=availability_zone)
def volume_delete(request, volume_id):
@ -163,3 +167,25 @@ def tenant_absolute_limits(request):
else:
limits_dict[limit.name] = limit.value
return limits_dict
def availability_zone_list(request, detailed=False):
return cinderclient(request).availability_zones.list(detailed=detailed)
@memoized
def list_extensions(request):
return cinder_list_extensions.ListExtManager(cinderclient(request))\
.show_all()
@memoized
def extension_supported(request, extension_name):
"""
This method will determine if Cinder supports a given extension name.
"""
extensions = list_extensions(request)
for extension in extensions:
if extension.name == extension_name:
return True
return False

View File

@ -51,6 +51,8 @@ class CreateForm(forms.SelfHandlingForm):
data_attrs=('size', 'name'),
transform=lambda x: "%s (%s)" % (x.name, filesizeformat(x.bytes))),
required=False)
availability_zone = forms.ChoiceField(label=_("Availability Zone"),
required=False)
def __init__(self, request, *args, **kwargs):
super(CreateForm, self).__init__(request, *args, **kwargs)
@ -58,6 +60,8 @@ class CreateForm(forms.SelfHandlingForm):
self.fields['type'].choices = [("", "")] + \
[(type.name, type.name)
for type in volume_types]
self.fields['availability_zone'].choices = \
self.availability_zones(request)
if ("snapshot_id" in request.GET):
try:
@ -137,6 +141,34 @@ class CreateForm(forms.SelfHandlingForm):
else:
del self.fields['volume_source_type']
# Determine whether the extension for Cinder AZs is enabled
def cinder_az_supported(self, request):
try:
return cinder.extension_supported(request, 'AvailabilityZones')
except Exception:
exceptions.handle(request, _('Unable to determine if '
'availability zones extension '
'is supported.'))
return False
def availability_zones(self, request):
zone_list = []
if self.cinder_az_supported(request):
try:
zones = api.cinder.availability_zone_list(request)
zone_list = [(zone.zoneName, zone.zoneName)
for zone in zones if zone.zoneState['available']]
zone_list.sort()
except Exception:
exceptions.handle(request, _('Unable to retrieve availability '
'zones.'))
if not zone_list:
zone_list.insert(0, ("", _("No availability zones found.")))
elif len(zone_list) > 0:
zone_list.insert(0, ("", _("Any Availability Zone")))
return zone_list
def handle(self, request, data):
try:
usages = quotas.tenant_limit_usages(self.request)
@ -188,6 +220,8 @@ class CreateForm(forms.SelfHandlingForm):
metadata = {}
az = data['availability_zone'] or None
volume = cinder.volume_create(request,
data['size'],
data['name'],
@ -195,7 +229,8 @@ class CreateForm(forms.SelfHandlingForm):
data['type'],
snapshot_id=snapshot_id,
image_id=image_id,
metadata=metadata)
metadata=metadata,
availability_zone=az)
message = _('Creating volume "%s"') % data['name']
messages.info(request, message)
return volume

View File

@ -199,6 +199,8 @@ class VolumesTable(VolumesTableBase):
empty_value="-")
attachments = AttachmentColumn("attachments",
verbose_name=_("Attached To"))
availability_zone = tables.Column("availability_zone",
verbose_name=_("Availability Zone"))
class Meta:
name = "volumes"

View File

@ -35,12 +35,15 @@ from openstack_dashboard.usage import quotas
class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_create',
'volume_snapshot_list',
'volume_type_list',),
'volume_type_list',
'availability_zone_list',
'extension_supported'),
api.glance: ('image_list_detailed',),
quotas: ('tenant_limit_usages',)})
def test_create_volume(self):
volume = self.volumes.first()
volume_type = self.volume_types.first()
az = self.cinder_availability_zones.first().zoneName
usage_limit = {'maxTotalVolumeGigabytes': 250,
'gigabytesUsed': 20,
'volumesUsed': len(self.volumes.list()),
@ -50,7 +53,8 @@ class VolumeViewTests(test.TestCase):
'method': u'CreateForm',
'type': volume_type.name,
'size': 50,
'snapshot_source': ''}
'snapshot_source': '',
'availability_zone': az}
cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list())
@ -66,6 +70,12 @@ class VolumeViewTests(test.TestCase):
filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \
.AndReturn([[], False])
cinder.availability_zone_list(IsA(http.HttpRequest)).AndReturn(
self.cinder_availability_zones.list())
cinder.extension_supported(IsA(http.HttpRequest), 'AvailabilityZones')\
.AndReturn(True)
cinder.volume_create(IsA(http.HttpRequest),
formData['size'],
formData['name'],
@ -73,7 +83,9 @@ class VolumeViewTests(test.TestCase):
formData['type'],
metadata={},
snapshot_id=None,
image_id=None).AndReturn(volume)
image_id=None,
availability_zone=formData['availability_zone'])\
.AndReturn(volume)
self.mox.ReplayAll()
@ -85,7 +97,9 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_create',
'volume_snapshot_list',
'volume_type_list',),
'volume_type_list',
'availability_zone_list',
'extension_supported'),
api.glance: ('image_list_detailed',),
quotas: ('tenant_limit_usages',)})
def test_create_volume_dropdown(self):
@ -117,6 +131,12 @@ class VolumeViewTests(test.TestCase):
.AndReturn([[], False])
quotas.tenant_limit_usages(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.extension_supported(IsA(http.HttpRequest), 'AvailabilityZones')\
.AndReturn(True)
cinder.availability_zone_list(IsA(http.HttpRequest)).AndReturn(
self.cinder_availability_zones.list())
cinder.volume_create(IsA(http.HttpRequest),
formData['size'],
formData['name'],
@ -124,8 +144,8 @@ class VolumeViewTests(test.TestCase):
'',
metadata={},
snapshot_id=None,
image_id=None).\
AndReturn(volume)
image_id=None,
availability_zone=None).AndReturn(volume)
self.mox.ReplayAll()
@ -138,7 +158,9 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_create',
'volume_snapshot_get',
'volume_get',
'volume_type_list',),
'volume_type_list',
'availability_zone_list',
'extension_supported'),
quotas: ('tenant_limit_usages',)})
def test_create_volume_from_snapshot(self):
volume = self.volumes.first()
@ -162,6 +184,12 @@ class VolumeViewTests(test.TestCase):
str(snapshot.id)).AndReturn(snapshot)
cinder.volume_get(IsA(http.HttpRequest), snapshot.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())
cinder.volume_create(IsA(http.HttpRequest),
formData['size'],
formData['name'],
@ -169,8 +197,8 @@ class VolumeViewTests(test.TestCase):
'',
metadata={},
snapshot_id=snapshot.id,
image_id=None).\
AndReturn(volume)
image_id=None,
availability_zone=None).AndReturn(volume)
self.mox.ReplayAll()
# get snapshot from url
@ -186,7 +214,9 @@ class VolumeViewTests(test.TestCase):
'volume_snapshot_list',
'volume_snapshot_get',
'volume_get',
'volume_type_list',),
'volume_type_list',
'availability_zone_list',
'extension_supported'),
api.glance: ('image_list_detailed',),
quotas: ('tenant_limit_usages',)})
def test_create_volume_from_snapshot_dropdown(self):
@ -220,6 +250,12 @@ class VolumeViewTests(test.TestCase):
AndReturn(usage_limit)
cinder.volume_snapshot_get(IsA(http.HttpRequest),
str(snapshot.id)).AndReturn(snapshot)
cinder.extension_supported(IsA(http.HttpRequest), 'AvailabilityZones')\
.AndReturn(True)
cinder.availability_zone_list(IsA(http.HttpRequest)).AndReturn(
self.cinder_availability_zones.list())
cinder.volume_create(IsA(http.HttpRequest),
formData['size'],
formData['name'],
@ -227,8 +263,8 @@ class VolumeViewTests(test.TestCase):
'',
metadata={},
snapshot_id=snapshot.id,
image_id=None).\
AndReturn(volume)
image_id=None,
availability_zone=None).AndReturn(volume)
self.mox.ReplayAll()
@ -241,7 +277,9 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_snapshot_get',
'volume_type_list',
'volume_get',),
'volume_get',
'availability_zone_list',
'extension_supported'),
api.glance: ('image_list_detailed',),
quotas: ('tenant_limit_usages',)})
def test_create_volume_from_snapshot_invalid_size(self):
@ -263,6 +301,12 @@ class VolumeViewTests(test.TestCase):
str(snapshot.id)).AndReturn(snapshot)
cinder.volume_get(IsA(http.HttpRequest), snapshot.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())
quotas.tenant_limit_usages(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
@ -278,7 +322,9 @@ class VolumeViewTests(test.TestCase):
"snapshot size (40GB)")
@test.create_stubs({cinder: ('volume_create',
'volume_type_list',),
'volume_type_list',
'availability_zone_list',
'extension_supported'),
api.glance: ('image_get',),
quotas: ('tenant_limit_usages',)})
def test_create_volume_from_image(self):
@ -301,6 +347,12 @@ class VolumeViewTests(test.TestCase):
AndReturn(usage_limit)
api.glance.image_get(IsA(http.HttpRequest),
str(image.id)).AndReturn(image)
cinder.extension_supported(IsA(http.HttpRequest), 'AvailabilityZones')\
.AndReturn(True)
cinder.availability_zone_list(IsA(http.HttpRequest)).AndReturn(
self.cinder_availability_zones.list())
cinder.volume_create(IsA(http.HttpRequest),
formData['size'],
formData['name'],
@ -308,8 +360,8 @@ class VolumeViewTests(test.TestCase):
'',
metadata={},
snapshot_id=None,
image_id=image.id).\
AndReturn(volume)
image_id=image.id,
availability_zone=None).AndReturn(volume)
self.mox.ReplayAll()
@ -324,7 +376,9 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_create',
'volume_type_list',
'volume_snapshot_list',),
'volume_snapshot_list',
'availability_zone_list',
'extension_supported'),
api.glance: ('image_get',
'image_list_detailed'),
quotas: ('tenant_limit_usages',)})
@ -360,6 +414,12 @@ class VolumeViewTests(test.TestCase):
.AndReturn(usage_limit)
api.glance.image_get(IsA(http.HttpRequest),
str(image.id)).AndReturn(image)
cinder.extension_supported(IsA(http.HttpRequest), 'AvailabilityZones')\
.AndReturn(True)
cinder.availability_zone_list(IsA(http.HttpRequest)).AndReturn(
self.cinder_availability_zones.list())
cinder.volume_create(IsA(http.HttpRequest),
formData['size'],
formData['name'],
@ -367,8 +427,8 @@ class VolumeViewTests(test.TestCase):
'',
metadata={},
snapshot_id=None,
image_id=image.id).\
AndReturn(volume)
image_id=image.id,
availability_zone=None).AndReturn(volume)
self.mox.ReplayAll()
@ -379,7 +439,9 @@ class VolumeViewTests(test.TestCase):
redirect_url = reverse('horizon:project:volumes:index')
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({cinder: ('volume_type_list',),
@test.create_stubs({cinder: ('volume_type_list',
'availability_zone_list',
'extension_supported'),
api.glance: ('image_get',
'image_list_detailed'),
quotas: ('tenant_limit_usages',)})
@ -400,6 +462,10 @@ class VolumeViewTests(test.TestCase):
AndReturn(usage_limit)
api.glance.image_get(IsA(http.HttpRequest),
str(image.id)).AndReturn(image)
cinder.extension_supported(IsA(http.HttpRequest), 'AvailabilityZones')\
.AndReturn(True)
cinder.availability_zone_list(IsA(http.HttpRequest)).AndReturn(
self.cinder_availability_zones.list())
quotas.tenant_limit_usages(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
@ -414,7 +480,10 @@ class VolumeViewTests(test.TestCase):
"The volume size cannot be less than the "
"image size (20.0 GB)")
@test.create_stubs({cinder: ('volume_snapshot_list', 'volume_type_list',),
@test.create_stubs({cinder: ('volume_snapshot_list',
'volume_type_list',
'availability_zone_list',
'extension_supported'),
api.glance: ('image_list_detailed',),
quotas: ('tenant_limit_usages',)})
def test_create_volume_gb_used_over_alloted_quota(self):
@ -441,6 +510,10 @@ class VolumeViewTests(test.TestCase):
filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \
.AndReturn([[], False])
cinder.extension_supported(IsA(http.HttpRequest), 'AvailabilityZones')\
.AndReturn(True)
cinder.availability_zone_list(IsA(http.HttpRequest)).AndReturn(
self.cinder_availability_zones.list())
quotas.tenant_limit_usages(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
@ -453,7 +526,10 @@ class VolumeViewTests(test.TestCase):
' have 20GB of your quota available.']
self.assertEqual(res.context['form'].errors['__all__'], expected_error)
@test.create_stubs({cinder: ('volume_snapshot_list', 'volume_type_list',),
@test.create_stubs({cinder: ('volume_snapshot_list',
'volume_type_list',
'availability_zone_list',
'extension_supported'),
api.glance: ('image_list_detailed',),
quotas: ('tenant_limit_usages',)})
def test_create_volume_number_over_alloted_quota(self):
@ -480,6 +556,10 @@ class VolumeViewTests(test.TestCase):
filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \
.AndReturn([[], False])
cinder.extension_supported(IsA(http.HttpRequest), 'AvailabilityZones')\
.AndReturn(True)
cinder.availability_zone_list(IsA(http.HttpRequest)).AndReturn(
self.cinder_availability_zones.list())
quotas.tenant_limit_usages(IsA(http.HttpRequest)).\
AndReturn(usage_limit)

View File

@ -12,7 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.v1 import availability_zones
from cinderclient.v1 import quotas
from openstack_dashboard.api import base
from openstack_dashboard.usage import quotas as usage_quotas
@ -22,6 +24,7 @@ from openstack_dashboard.test.test_data import utils
def data(TEST):
TEST.cinder_quotas = utils.TestDataContainer()
TEST.cinder_quota_usages = utils.TestDataContainer()
TEST.cinder_availability_zones = utils.TestDataContainer()
# Quota Sets
quota_data = dict(volumes='1',
@ -44,3 +47,18 @@ def data(TEST):
quota_usage.tally(k, v['used'])
TEST.cinder_quota_usages.add(quota_usage)
# Availability Zones
# Cinder returns the following structure from os-availability-zone
# {"availabilityZoneInfo":
# [{"zoneState": {"available": true}, "zoneName": "nova"}]}
# Note that the default zone is still "nova" even though this is cinder
TEST.cinder_availability_zones.add(
availability_zones.AvailabilityZone(
availability_zones.AvailabilityZoneManager(None),
{
'zoneName': 'nova',
'zoneState': {'available': True}
}
)
)