diff --git a/openstack_dashboard/api/cinder.py b/openstack_dashboard/api/cinder.py
index ef574bab90..de69d907a5 100644
--- a/openstack_dashboard/api/cinder.py
+++ b/openstack_dashboard/api/cinder.py
@@ -370,6 +370,32 @@ def volume_backup_restore(request, backup_id, volume_id):
volume_id=volume_id)
+def volume_manage(request,
+ host,
+ identifier,
+ id_type,
+ name,
+ description,
+ volume_type,
+ availability_zone,
+ metadata,
+ bootable):
+ source = {id_type: identifier}
+ return cinderclient(request).volumes.manage(
+ host=host,
+ ref=source,
+ name=name,
+ description=description,
+ volume_type=volume_type,
+ availability_zone=availability_zone,
+ metadata=metadata,
+ bootable=bootable)
+
+
+def volume_unmanage(request, volume_id):
+ return cinderclient(request).volumes.unmanage(volume=volume_id)
+
+
def tenant_quota_get(request, tenant_id):
c_client = cinderclient(request)
if c_client is None:
diff --git a/openstack_dashboard/conf/cinder_policy.json b/openstack_dashboard/conf/cinder_policy.json
index b20bb853df..fde92f146d 100644
--- a/openstack_dashboard/conf/cinder_policy.json
+++ b/openstack_dashboard/conf/cinder_policy.json
@@ -33,6 +33,9 @@
"volume_extension:quotas:update": [["rule:admin_api"]],
"volume_extension:quota_classes": [],
+ "volume_extension:volume_manage": [["rule:admin_api"]],
+ "volume_extension:volume_unmanage": [["rule:admin_api"]],
+
"volume_extension:volume_admin_actions:reset_status": [["rule:admin_api"]],
"volume_extension:snapshot_admin_actions:reset_status": [["rule:admin_api"]],
"volume_extension:volume_admin_actions:force_delete": [["rule:admin_api"]],
diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/_manage_volume.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/_manage_volume.html
new file mode 100644
index 0000000000..4fb0571a48
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/_manage_volume.html
@@ -0,0 +1,14 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+
+{% block modal-body-right %}
+
{% trans "Description:" %}
+ {% blocktrans %}
+ "Manage" an existing volume from a Cinder host. This will make the volume visible within
+ OpenStack.
+
+
+ This is equivalent to the cinder manage command.
+ {% endblocktrans %}
+
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/_unmanage_volume.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/_unmanage_volume.html
new file mode 100644
index 0000000000..31b6622cdc
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/_unmanage_volume.html
@@ -0,0 +1,14 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+
+{% block modal-body-right %}
+ {% trans "Description:" %}
+ {% blocktrans %}
+ When a volume is "unmanaged", the volume will no longer be visible within OpenStack. Note that the
+ volume will not be deleted from the Cinder host.
+
+
+ This is equivalent to the cinder unmanage command.
+ {% endblocktrans %}
+
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/manage_volume.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/manage_volume.html
new file mode 100644
index 0000000000..f163cddefa
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/manage_volume.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Manage Volume" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Manage a Volume") %}
+{% endblock page_header %}
+
+{% block main %}
+ {% include 'project/volumes/volumes/_manage_volume.html' %}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/unmanage_volume.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/unmanage_volume.html
new file mode 100644
index 0000000000..6190c0b47b
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/unmanage_volume.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Unmanage Volume" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Unmanage a Volume") %}
+{% endblock page_header %}
+
+{% block main %}
+ {% include 'project/volumes/volumes/_unmanage_volume.html' %}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/volumes/tests.py b/openstack_dashboard/dashboards/admin/volumes/tests.py
index c85525163f..4dab97e25c 100644
--- a/openstack_dashboard/dashboards/admin/volumes/tests.py
+++ b/openstack_dashboard/dashboards/admin/volumes/tests.py
@@ -19,6 +19,7 @@ from mox import IsA # noqa
from openstack_dashboard import api
from openstack_dashboard.api import cinder
from openstack_dashboard.api import keystone
+from openstack_dashboard.dashboards.admin.volumes.volumes import forms
from openstack_dashboard.test import helpers as test
@@ -60,6 +61,89 @@ class VolumeTests(test.BaseAdminViewTests):
formData)
self.assertNoFormErrors(res)
+ @test.create_stubs({cinder: ('volume_manage',
+ 'volume_type_list',
+ 'availability_zone_list',
+ 'extension_supported')})
+ def test_manage_volume(self):
+ metadata = {'key': u'k1',
+ 'value': u'v1'}
+ formData = {'host': 'host-1',
+ 'identifier': 'vol-1',
+ 'id_type': u'source-name',
+ 'name': 'name-1',
+ 'description': 'manage a volume',
+ 'volume_type': 'vol_type_1',
+ 'availability_zone': 'nova',
+ 'metadata': metadata['key'] + '=' + metadata['value'],
+ 'bootable': False}
+ cinder.volume_type_list(
+ IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.list())
+ cinder.availability_zone_list(
+ IsA(http.HttpRequest)).\
+ AndReturn(self.availability_zones.list())
+ cinder.extension_supported(
+ IsA(http.HttpRequest),
+ 'AvailabilityZones').\
+ AndReturn(True)
+ cinder.volume_manage(
+ IsA(http.HttpRequest),
+ host=formData['host'],
+ identifier=formData['identifier'],
+ id_type=formData['id_type'],
+ name=formData['name'],
+ description=formData['description'],
+ volume_type=formData['volume_type'],
+ availability_zone=formData['availability_zone'],
+ metadata={metadata['key']: metadata['value']},
+ bootable=formData['bootable'])
+ self.mox.ReplayAll()
+ res = self.client.post(
+ reverse('horizon:admin:volumes:volumes:manage'),
+ formData)
+ self.assertNoFormErrors(res)
+
+ def test_manage_volume_extra_specs(self):
+ # these should pass
+ forms.validate_metadata("key1=val1")
+ forms.validate_metadata("key1=val1,key2=val2")
+ forms.validate_metadata("key1=val1,key2=val2,key3=val3")
+ forms.validate_metadata("key1=")
+
+ # these should throw a validation error
+ self.assertRaises(forms.ValidationError,
+ forms.validate_metadata, "key1==val1")
+ self.assertRaises(forms.ValidationError,
+ forms.validate_metadata, "key1=val1,")
+ self.assertRaises(forms.ValidationError,
+ forms.validate_metadata, "=val1")
+ self.assertRaises(forms.ValidationError,
+ forms.validate_metadata, ",")
+ self.assertRaises(forms.ValidationError,
+ forms.validate_metadata, " ")
+
+ @test.create_stubs({cinder: ('volume_unmanage',
+ 'volume_get')})
+ def test_unmanage_volume(self):
+ # important - need to get the v2 cinder volume which has host data
+ volume_list = \
+ filter(lambda x: x.name == 'v2_volume', self.cinder_volumes.list())
+ volume = volume_list[0]
+ formData = {'volume_name': volume.name,
+ 'host_name': 'host@backend-name#pool',
+ 'volume_id': volume.id}
+
+ cinder.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume)
+ cinder.volume_unmanage(IsA(http.HttpRequest), volume.id).\
+ AndReturn(volume)
+ self.mox.ReplayAll()
+ res = self.client.post(
+ reverse('horizon:admin:volumes:volumes:unmanage',
+ args=(volume.id,)),
+ formData)
+ self.assertNoFormErrors(res)
+
@test.create_stubs({cinder: ('volume_type_list_with_qos_associations',
'qos_spec_list',
'extension_supported',
diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/forms.py b/openstack_dashboard/dashboards/admin/volumes/volumes/forms.py
index b3303be447..49993e6bcc 100644
--- a/openstack_dashboard/dashboards/admin/volumes/volumes/forms.py
+++ b/openstack_dashboard/dashboards/admin/volumes/volumes/forms.py
@@ -16,6 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+from django.forms import ValidationError # noqa
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
@@ -23,6 +24,142 @@ from horizon import forms
from horizon import messages
from openstack_dashboard.api import cinder
+from openstack_dashboard.dashboards.project.volumes.volumes \
+ import forms as project_forms
+
+
+def validate_metadata(value):
+ error_msg = _('Invalid metadata entry. Use comma-separated'
+ ' key=value pairs')
+
+ if value:
+ specs = value.split(",")
+ for spec in specs:
+ keyval = spec.split("=")
+ # ensure both sides of "=" exist, but allow blank value
+ if not len(keyval) == 2 or not keyval[0]:
+ raise ValidationError(error_msg)
+
+
+class ManageVolume(forms.SelfHandlingForm):
+ identifier = forms.CharField(
+ max_length=255,
+ label=_("Identifier"),
+ help_text=_("Name or other identifier for existing volume"))
+ id_type = forms.ChoiceField(
+ label=_("Identifier Type"),
+ help_text=_("Type of backend device identifier provided"))
+ host = forms.CharField(
+ max_length=255,
+ label=_("Host"),
+ help_text=_("Cinder host on which the existing volume resides; "
+ "takes the form: host@backend-name#pool"))
+ name = forms.CharField(
+ max_length=255,
+ label=_("Volume Name"),
+ required=False,
+ help_text=_("Volume name to be assigned"))
+ description = forms.CharField(max_length=255, widget=forms.Textarea(
+ attrs={'class': 'modal-body-fixed-width', 'rows': 4}),
+ label=_("Description"), required=False)
+ metadata = forms.CharField(max_length=255, widget=forms.Textarea(
+ attrs={'class': 'modal-body-fixed-width', 'rows': 2}),
+ label=_("Metadata"), required=False,
+ help_text=_("Comma-separated key=value pairs"),
+ validators=[validate_metadata])
+ volume_type = forms.ChoiceField(
+ label=_("Volume Type"),
+ required=False)
+ availability_zone = forms.ChoiceField(
+ label=_("Availability Zone"),
+ required=False)
+
+ bootable = forms.BooleanField(
+ label=_("Bootable"),
+ required=False,
+ help_text=_("Specifies that the newly created volume "
+ "should be marked as bootable"))
+
+ def __init__(self, request, *args, **kwargs):
+ super(ManageVolume, self).__init__(request, *args, **kwargs)
+ self.fields['id_type'].choices = [("source-name", _("Name"))] + \
+ [("source-id", _("ID"))]
+ volume_types = cinder.volume_type_list(request)
+ self.fields['volume_type'].choices = [("", _("No volume type"))] + \
+ [(type.name, type.name)
+ for type in volume_types]
+ self.fields['availability_zone'].choices = \
+ project_forms.availability_zones(request)
+
+ def handle(self, request, data):
+ try:
+ az = data.get('availability_zone')
+
+ # assume user enters metadata with "key1=val1,key2=val2"
+ # convert to dictionary
+ metadataDict = {}
+ metadata = data.get('metadata')
+ if metadata:
+ metadata.replace(" ", "")
+ for item in metadata.split(','):
+ key, value = item.split('=')
+ metadataDict[key] = value
+
+ cinder.volume_manage(request,
+ host=data['host'],
+ identifier=data['identifier'],
+ id_type=data['id_type'],
+ name=data['name'],
+ description=data['description'],
+ volume_type=data['volume_type'],
+ availability_zone=az,
+ metadata=metadataDict,
+ bootable=data['bootable'])
+
+ # for success message, use identifier if user does not
+ # provide a volume name
+ volume_name = data['name']
+ if not volume_name:
+ volume_name = data['identifier']
+
+ messages.success(
+ request,
+ _('Successfully sent the request to manage volume: %s')
+ % volume_name)
+ return True
+ except Exception:
+ exceptions.handle(request, _("Unable to manage volume."))
+ return False
+
+
+class UnmanageVolume(forms.SelfHandlingForm):
+ name = forms.CharField(label=_("Volume Name"),
+ required=False,
+ widget=forms.TextInput(
+ attrs={'readonly': 'readonly'}))
+ host = forms.CharField(label=_("Host"),
+ required=False,
+ widget=forms.TextInput(
+ attrs={'readonly': 'readonly'}))
+ volume_id = forms.CharField(label=_("ID"),
+ required=False,
+ widget=forms.TextInput(
+ attrs={'readonly': 'readonly'}))
+
+ def __init__(self, request, *args, **kwargs):
+ super(UnmanageVolume, self).__init__(request, *args, **kwargs)
+
+ def handle(self, request, data):
+ try:
+ cinder.volume_unmanage(request, self.initial['volume_id'])
+ messages.success(
+ request,
+ _('Successfully sent the request to unmanage volume: %s')
+ % data['name'])
+ return True
+ except Exception:
+ exceptions.handle(request, _("Unable to unmanage volume."))
+ return False
class CreateVolumeType(forms.SelfHandlingForm):
diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/tables.py b/openstack_dashboard/dashboards/admin/volumes/volumes/tables.py
index 694f1733ae..6e4d3f4bd7 100644
--- a/openstack_dashboard/dashboards/admin/volumes/volumes/tables.py
+++ b/openstack_dashboard/dashboards/admin/volumes/volumes/tables.py
@@ -12,7 +12,9 @@
from django.utils.translation import ugettext_lazy as _
+from horizon import exceptions
from horizon import tables
+
from openstack_dashboard.dashboards.project.volumes \
.volumes import tables as volumes_tables
@@ -26,6 +28,42 @@ class VolumesFilterAction(tables.FilterAction):
if q in volume.name.lower()]
+class ManageVolumeAction(tables.LinkAction):
+ name = "manage"
+ verbose_name = _("Manage Volume")
+ url = "horizon:admin:volumes:volumes:manage"
+ classes = ("ajax-modal",)
+ icon = "plus"
+ policy_rules = (("volume", "volume_extension:volume_manage"),)
+ ajax = True
+
+
+class UnmanageVolumeAction(tables.LinkAction):
+ name = "unmanage"
+ verbose_name = _("Unmanage Volume")
+ url = "horizon:admin:volumes:volumes:unmanage"
+ classes = ("ajax-modal",)
+ icon = "pencil"
+ policy_rules = (("volume", "volume_extension:volume_unmanage"),)
+
+ def allowed(self, request, volume=None):
+ # don't allow unmanage if volume is attached to instance or
+ # volume has snapshots
+ if volume:
+ if volume.attachments:
+ return False
+
+ try:
+ return (volume.status in volumes_tables.DELETABLE_STATES and
+ not getattr(volume, 'has_snapshot', False))
+ except Exception:
+ exceptions.handle(request,
+ _("Unable to retrieve snapshot data."))
+ return False
+
+ return False
+
+
class UpdateVolumeStatusAction(tables.LinkAction):
name = "update_status"
verbose_name = _("Update Volume Status")
@@ -48,7 +86,11 @@ class VolumesTable(volumes_tables.VolumesTable):
verbose_name = _("Volumes")
status_columns = ["status"]
row_class = volumes_tables.UpdateRow
- table_actions = (volumes_tables.DeleteVolume, VolumesFilterAction)
- row_actions = (volumes_tables.DeleteVolume, UpdateVolumeStatusAction)
+ table_actions = (ManageVolumeAction,
+ volumes_tables.DeleteVolume,
+ VolumesFilterAction)
+ row_actions = (volumes_tables.DeleteVolume,
+ UpdateVolumeStatusAction,
+ UnmanageVolumeAction)
columns = ('tenant', 'host', 'name', 'size', 'status', 'volume_type',
'attachments', 'bootable', 'encryption',)
diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/urls.py b/openstack_dashboard/dashboards/admin/volumes/volumes/urls.py
index 15390cd3bf..d5665897e3 100644
--- a/openstack_dashboard/dashboards/admin/volumes/volumes/urls.py
+++ b/openstack_dashboard/dashboards/admin/volumes/volumes/urls.py
@@ -20,8 +20,16 @@ VIEWS_MOD = ('openstack_dashboard.dashboards.admin.volumes.volumes.views')
urlpatterns = patterns(
VIEWS_MOD,
- url(r'^(?P[^/]+)/$', views.DetailView.as_view(),
+ url(r'^manage/$',
+ views.ManageVolumeView.as_view(),
+ name='manage'),
+ url(r'^(?P[^/]+)/$',
+ views.DetailView.as_view(),
name='detail'),
url(r'^(?P[^/]+)/update_status$',
- views.UpdateStatusView.as_view(), name='update_status'),
+ views.UpdateStatusView.as_view(),
+ name='update_status'),
+ url(r'^(?P[^/]+)/unmanage$',
+ views.UnmanageVolumeView.as_view(),
+ name='unmanage'),
)
diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/views.py b/openstack_dashboard/dashboards/admin/volumes/volumes/views.py
index f28ddc14e3..745d00045d 100644
--- a/openstack_dashboard/dashboards/admin/volumes/volumes/views.py
+++ b/openstack_dashboard/dashboards/admin/volumes/volumes/views.py
@@ -40,6 +40,55 @@ class DetailView(volumes_views.DetailView):
return reverse('horizon:admin:volumes:index')
+class ManageVolumeView(forms.ModalFormView):
+ form_class = volumes_forms.ManageVolume
+ template_name = 'admin/volumes/volumes/manage_volume.html'
+ modal_header = _("Manage Volume")
+ form_id = "manage_volume_modal"
+ submit_label = _("Manage")
+ success_url = reverse_lazy('horizon:admin:volumes:volumes_tab')
+ submit_url = reverse_lazy('horizon:admin:volumes:volumes:manage')
+ cancel_url = reverse_lazy("horizon:admin:volumes:index")
+
+ def get_context_data(self, **kwargs):
+ context = super(ManageVolumeView, self).get_context_data(**kwargs)
+ return context
+
+
+class UnmanageVolumeView(forms.ModalFormView):
+ form_class = volumes_forms.UnmanageVolume
+ template_name = 'admin/volumes/volumes/unmanage_volume.html'
+ modal_header = _("Confirm Unmanage Volume")
+ form_id = "unmanage_volume_modal"
+ submit_label = _("Unmanage")
+ success_url = reverse_lazy('horizon:admin:volumes:volumes_tab')
+ submit_url = 'horizon:admin:volumes:volumes:unmanage'
+ cancel_url = reverse_lazy("horizon:admin:volumes:index")
+
+ def get_context_data(self, **kwargs):
+ context = super(UnmanageVolumeView, self).get_context_data(**kwargs)
+ args = (self.kwargs['volume_id'],)
+ context['submit_url'] = reverse(self.submit_url, args=args)
+ return context
+
+ @memoized.memoized_method
+ def get_data(self):
+ try:
+ volume_id = self.kwargs['volume_id']
+ volume = cinder.volume_get(self.request, volume_id)
+ except Exception:
+ exceptions.handle(self.request,
+ _('Unable to retrieve volume details.'),
+ redirect=self.success_url)
+ return volume
+
+ def get_initial(self):
+ volume = self.get_data()
+ return {'volume_id': self.kwargs["volume_id"],
+ 'name': volume.name,
+ 'host': getattr(volume, "os-vol-host-attr:host")}
+
+
class CreateVolumeTypeView(forms.ModalFormView):
form_class = volumes_forms.CreateVolumeType
template_name = 'admin/volumes/volumes/create_volume_type.html'
diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/forms.py b/openstack_dashboard/dashboards/project/volumes/volumes/forms.py
index 39564975af..70929a3d74 100644
--- a/openstack_dashboard/dashboards/project/volumes/volumes/forms.py
+++ b/openstack_dashboard/dashboards/project/volumes/volumes/forms.py
@@ -45,6 +45,35 @@ VALID_DISK_FORMATS = ('raw', 'vmdk', 'vdi', 'qcow2')
DEFAULT_CONTAINER_FORMAT = 'bare'
+# Determine whether the extension for Cinder AZs is enabled
+def cinder_az_supported(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(request):
+ zone_list = []
+ if 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
+
+
class CreateForm(forms.SelfHandlingForm):
name = forms.CharField(max_length=255, label=_("Volume Name"),
required=False)
@@ -124,7 +153,7 @@ class CreateForm(forms.SelfHandlingForm):
def prepare_source_fields_if_image_specified(self, request):
self.fields['availability_zone'].choices = \
- self.availability_zones(request)
+ availability_zones(request)
try:
image = self.get_image(request,
request.GET["image_id"])
@@ -156,7 +185,7 @@ class CreateForm(forms.SelfHandlingForm):
def prepare_source_fields_if_volume_specified(self, request):
self.fields['availability_zone'].choices = \
- self.availability_zones(request)
+ availability_zones(request)
volume = None
try:
volume = self.get_volume(request, request.GET["volume_id"])
@@ -182,7 +211,7 @@ class CreateForm(forms.SelfHandlingForm):
def prepare_source_fields_default(self, request):
source_type_choices = []
self.fields['availability_zone'].choices = \
- self.availability_zones(request)
+ availability_zones(request)
try:
available = api.cinder.VOLUME_STATE_AVAILABLE
@@ -264,34 +293,6 @@ class CreateForm(forms.SelfHandlingForm):
self._errors['volume_source'] = self.error_class([msg])
return cleaned_data
- # 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 get_volumes(self, request):
volumes = []
try:
diff --git a/openstack_dashboard/test/test_data/cinder_data.py b/openstack_dashboard/test/test_data/cinder_data.py
index ae69bd7496..b99696a6c7 100644
--- a/openstack_dashboard/test/test_data/cinder_data.py
+++ b/openstack_dashboard/test/test_data/cinder_data.py
@@ -158,6 +158,7 @@ def data(TEST):
'size': 20,
'created_at': '2014-01-27 10:30:00',
'volume_type': None,
+ 'os-vol-host-attr:host': 'host@backend-name#pool',
'bootable': 'true',
'attachments': []})
volume_v2.bootable = 'true'