Add widget for volume selection

Murano provides support for attaching Cinder volumes to instances and
booting instances from existing volumes. However, there is no way for
a user to select an existing volume (or volume snapshot) using the
murano dynamic UI.
This patch provides a way to show and select available volumes or
volume snapshots.

Change-Id: Ic99443384180c8d0df33e2b7c15b8e56cd086da3
Implements: blueprint volume-selection-ui-element
This commit is contained in:
devray 2017-06-14 08:22:25 -04:00
parent 890cd2c41c
commit af297da1a8
4 changed files with 167 additions and 1 deletions

View File

@ -28,6 +28,7 @@ from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms as hz_forms
from horizon import messages
from openstack_dashboard.api import cinder
from openstack_dashboard.api import glance
from openstack_dashboard.api import network
from openstack_dashboard.api import nova
@ -551,6 +552,44 @@ class AZoneChoiceField(ChoiceField):
self.choices = az_choices
class VolumeChoiceField(ChoiceField):
def __init__(self,
include_snapshots=True,
*args,
**kwargs):
self.include_snapshots = include_snapshots
super(VolumeChoiceField, self).__init__(*args, **kwargs)
@with_request
def update(self, request, **kwargs):
"""This widget allows selection of Volumes and Volume Snapshots"""
available = {'status': cinder.VOLUME_STATE_AVAILABLE}
try:
choices = [(volume.id, volume.name)
for volume in cinder.volume_list(request,
search_opts=available)]
except Exception:
choices = []
exceptions.handle(request,
_("Unable to retrieve volume list."))
if self.include_snapshots:
try:
choices.extend((snap.id, snap.name)
for snap in cinder.volume_snapshot_list(request,
search_opts=available))
except Exception:
exceptions.handle(request,
_("Unable to retrieve snapshot list."))
if choices:
choices.sort(key=lambda e: e[1])
choices.insert(0, ("", _("Select volume")))
else:
choices.insert(0, ("", _("No volumes available")))
self.choices = choices
class BooleanField(forms.BooleanField, CustomPropertiesField):
def __init__(self, *args, **kwargs):
if 'widget' in kwargs:

View File

@ -51,7 +51,8 @@ TYPES.update({
'text': (fields.CharField, forms.Textarea),
'choice': fields.ChoiceField,
'floatingip': fields.FloatingIpBooleanField,
'securitygroup': fields.SecurityGroupChoiceField
'securitygroup': fields.SecurityGroupChoiceField,
'volume': fields.VolumeChoiceField
})
KEYPAIR_IMPORT_URL = "horizon:project:key_pairs:import"

View File

@ -676,6 +676,126 @@ class TestNetworkChoiceField(testtools.TestCase):
self.network_choice_field.to_python(None))
class TestVolumeChoiceField(testtools.TestCase):
def setUp(self):
super(TestVolumeChoiceField, self).setUp()
self.request = {'request': mock.Mock()}
self.addCleanup(mock.patch.stopall)
@mock.patch.object(fields, 'cinder')
def test_update(self, mock_cinder):
foo_vol = mock.Mock()
bar_snap = mock.Mock()
baz_snap = mock.Mock()
foo_vol.configure_mock(name='foo_vol', id='foo_id', status='available')
bar_snap.configure_mock(name='bar_snap', id='bar_id',
status='available')
baz_snap.configure_mock(name='baz_snap', id='baz_id', status='error')
mock_cinder.volume_list.return_value = [foo_vol]
mock_cinder.volume_snapshot_list.return_value = [bar_snap]
volume_choice_field = fields.VolumeChoiceField(include_snapshots=True)
volume_choice_field.choices = []
volume_choice_field.update(self.request)
expected_choices = [
('', _('Select volume')), ('foo_id', 'foo_vol'),
('bar_id', 'bar_snap')
]
self.assertEqual(sorted(expected_choices),
sorted(volume_choice_field.choices))
@mock.patch.object(fields, 'cinder')
def test_update_withoutsnapshot(self, mock_cinder):
foo_vol = mock.Mock()
bar_vol = mock.Mock()
baz_snap = mock.Mock()
foo_vol.configure_mock(name='foo_vol', id='foo_id', status='available')
bar_vol.configure_mock(name='bar_vol', id='bar_id', status='error')
baz_snap.configure_mock(name='baz_snap', id='baz_id',
status='available')
mock_cinder.volume_list.return_value = [foo_vol]
mock_cinder.volume_snapshot_list.return_value = [baz_snap]
volume_choice_field = fields.VolumeChoiceField(include_snapshots=False)
volume_choice_field.choices = []
volume_choice_field.update(self.request)
expected_choices = [
('', _('Select volume')), ('foo_id', 'foo_vol')
]
self.assertEqual(sorted(expected_choices),
sorted(volume_choice_field.choices))
@mock.patch.object(fields, 'exceptions')
@mock.patch.object(fields, 'cinder')
def test_update_except_snapshot_list_exception(self, mock_cinder,
mock_exceptions):
foo_vol = mock.Mock()
bar_vol = mock.Mock()
foo_vol.configure_mock(name='foo_vol', id='foo_id', status='available')
bar_vol.configure_mock(name='bar_vol', id='bar_id', status='error')
mock_cinder.volume_list.return_value = [foo_vol]
mock_cinder.volume_snapshot_list.side_effect = Exception
volume_choice_field = fields.VolumeChoiceField(include_snapshots=True)
volume_choice_field.choices = []
volume_choice_field.update(self.request)
expected_choices = [
('', _('Select volume')), ('foo_id', 'foo_vol')
]
self.assertEqual(sorted(expected_choices),
sorted(volume_choice_field.choices))
mock_exceptions.handle.assert_called_once_with(
self.request['request'], _('Unable to retrieve snapshot list.'))
@mock.patch.object(fields, 'exceptions')
@mock.patch.object(fields, 'cinder')
def test_update_except_volume_list_exception(self, mock_cinder,
mock_exceptions):
bar_snap = mock.Mock()
baz_snap = mock.Mock()
bar_snap.configure_mock(name='bar_snap', id='bar_id',
status='available')
baz_snap.configure_mock(name='baz_snap', id='baz_id', status='error')
mock_cinder.volume_list.side_effect = Exception
mock_cinder.volume_snapshot_list.return_value = [bar_snap]
volume_choice_field = fields.VolumeChoiceField(include_snapshots=True)
volume_choice_field.choices = []
volume_choice_field.update(self.request)
expected_choices = [
('', _('Select volume')), ('bar_id', 'bar_snap')
]
self.assertEqual(expected_choices, volume_choice_field.choices)
mock_exceptions.handle.assert_called_once_with(
self.request['request'], _('Unable to retrieve volume list.'))
@mock.patch.object(fields, 'exceptions')
@mock.patch.object(fields, 'cinder')
def test_update_except_exception(self, mock_cinder, mock_exceptions):
mock_cinder.volume_list.side_effect = Exception
mock_cinder.volume_snapshot_list.side_effect = Exception
volume_choice_field = fields.VolumeChoiceField(include_snapshots=True)
volume_choice_field.choices = []
volume_choice_field.update(self.request)
expected_choices = [
('', _('No volumes available'))
]
expected_calls = [
mock.call(self.request['request'],
_('Unable to retrieve volume list.')),
mock.call(self.request['request'],
_('Unable to retrieve snapshot list.'))
]
self.assertEqual(expected_choices, volume_choice_field.choices)
mock_exceptions.handle.assert_has_calls(expected_calls)
class TestAZoneChoiceField(testtools.TestCase):
@mock.patch.object(fields, 'nova')

View File

@ -0,0 +1,6 @@
---
features:
- |
Added a widget to display and select available volumes and volume
snapshots.