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:
parent
890cd2c41c
commit
af297da1a8
@ -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:
|
||||
|
@ -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"
|
||||
|
@ -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')
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Added a widget to display and select available volumes and volume
|
||||
snapshots.
|
||||
|
Loading…
Reference in New Issue
Block a user