New admin volume panel to manage/unmanage volumes.
Manage will take an existing volume created outside of Openstack and make it available. Unmanage will remove the visibility of a volume within Openstack, but will not delete the actual volume. Change-Id: I6df46f0944015833d1fb94611f9bf520ca8bca8b Implements: blueprint add-manage-unmanage-volume
This commit is contained in:
parent
4ae40a803e
commit
455ee822b4
@ -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:
|
||||
|
@ -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"]],
|
||||
|
@ -0,0 +1,14 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% blocktrans %}
|
||||
"Manage" an existing volume from a Cinder host. This will make the volume visible within
|
||||
OpenStack.
|
||||
<br>
|
||||
<br>
|
||||
This is equivalent to the <tt>cinder manage</tt> command.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% endblock %}
|
@ -0,0 +1,14 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% 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.
|
||||
<br>
|
||||
<br>
|
||||
This is equivalent to the <tt>cinder unmanage</tt> command.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% endblock %}
|
@ -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 %}
|
@ -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 %}
|
@ -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',
|
||||
|
@ -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):
|
||||
|
@ -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',)
|
||||
|
@ -20,8 +20,16 @@ VIEWS_MOD = ('openstack_dashboard.dashboards.admin.volumes.volumes.views')
|
||||
|
||||
urlpatterns = patterns(
|
||||
VIEWS_MOD,
|
||||
url(r'^(?P<volume_id>[^/]+)/$', views.DetailView.as_view(),
|
||||
url(r'^manage/$',
|
||||
views.ManageVolumeView.as_view(),
|
||||
name='manage'),
|
||||
url(r'^(?P<volume_id>[^/]+)/$',
|
||||
views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
url(r'^(?P<volume_id>[^/]+)/update_status$',
|
||||
views.UpdateStatusView.as_view(), name='update_status'),
|
||||
views.UpdateStatusView.as_view(),
|
||||
name='update_status'),
|
||||
url(r'^(?P<volume_id>[^/]+)/unmanage$',
|
||||
views.UnmanageVolumeView.as_view(),
|
||||
name='unmanage'),
|
||||
)
|
||||
|
@ -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'
|
||||
|
@ -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:
|
||||
|
@ -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'
|
||||
|
Loading…
x
Reference in New Issue
Block a user