Add volume extend functionality support
Cinder now has the ablity to extend a volume, This functionality should be exposed through Horizon. Change-Id: Iff26453fd0a83af97fad4adabbcd53f646a15e51 Implements: blueprint volume-extend
This commit is contained in:
parent
fdbf85c30a
commit
3c68470cfc
|
@ -101,6 +101,10 @@ def volume_create(request, size, name, description, volume_type,
|
|||
availability_zone=availability_zone)
|
||||
|
||||
|
||||
def volume_extend(request, volume_id, new_size):
|
||||
return cinderclient(request).volumes.extend(volume_id, new_size)
|
||||
|
||||
|
||||
def volume_delete(request, volume_id):
|
||||
return cinderclient(request).volumes.delete(volume_id)
|
||||
|
||||
|
|
|
@ -421,3 +421,36 @@ class UpdateForm(forms.SelfHandlingForm):
|
|||
exceptions.handle(request,
|
||||
_('Unable to update volume.'),
|
||||
redirect=redirect)
|
||||
|
||||
|
||||
class ExtendForm(forms.SelfHandlingForm):
|
||||
name = forms.CharField(label=_("Volume Name"),
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}
|
||||
))
|
||||
new_size = forms.IntegerField(min_value=1, label=_("Size (GB)"))
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(ExtendForm, self).clean()
|
||||
new_size = cleaned_data.get('new_size', 1)
|
||||
if new_size <= self.initial['orig_size']:
|
||||
raise ValidationError(
|
||||
_("New size for extend must be greater than current size."))
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
volume_id = self.initial['id']
|
||||
try:
|
||||
volume = cinder.volume_extend(request,
|
||||
volume_id,
|
||||
data['new_size'])
|
||||
|
||||
message = _('Successfully extended volume: "%s"') % data['name']
|
||||
messages.success(request, message)
|
||||
return volume
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:volumes:index")
|
||||
exceptions.handle(request,
|
||||
_('Unable to extend volume.'),
|
||||
redirect=redirect)
|
||||
|
|
|
@ -32,7 +32,7 @@ from openstack_dashboard import policy
|
|||
from openstack_dashboard.usage import quotas
|
||||
|
||||
|
||||
DELETABLE_STATES = ("available", "error")
|
||||
DELETABLE_STATES = ("available", "error", "error_extending")
|
||||
|
||||
|
||||
class DeleteVolume(tables.DeleteAction):
|
||||
|
@ -86,6 +86,23 @@ class CreateVolume(tables.LinkAction):
|
|||
return True
|
||||
|
||||
|
||||
class ExtendVolume(tables.LinkAction):
|
||||
name = "extend"
|
||||
verbose_name = _("Extend Volume")
|
||||
url = "horizon:project:volumes:extend"
|
||||
classes = ("ajax-modal", "btn-extend")
|
||||
policy_rules = (("volume", "volume:extend"),)
|
||||
|
||||
def get_policy_target(self, request, datum=None):
|
||||
project_id = None
|
||||
if datum:
|
||||
project_id = getattr(datum, "os-vol-tenant-attr:tenant_id", None)
|
||||
return {"project_id": project_id}
|
||||
|
||||
def allowed(self, request, volume=None):
|
||||
return volume.status in ("available", "in-use")
|
||||
|
||||
|
||||
class EditAttachments(tables.LinkAction):
|
||||
name = "attachments"
|
||||
verbose_name = _("Edit Attachments")
|
||||
|
@ -255,7 +272,7 @@ class VolumesTable(VolumesTableBase):
|
|||
status_columns = ["status"]
|
||||
row_class = UpdateRow
|
||||
table_actions = (CreateVolume, DeleteVolume, VolumesFilterAction)
|
||||
row_actions = (EditAttachments, EditVolume,
|
||||
row_actions = (EditVolume, ExtendVolume, EditAttachments,
|
||||
CreateSnapshot, DeleteVolume)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:project:volumes:extend' volume.id%}{% endblock %}
|
||||
|
||||
{% block modal_id %}extend_volume_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Extend Volume" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="right quota-dynamic">
|
||||
{% include "project/volumes/_extend_limits.html" with usages=usages %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Extend Volume" %}" />
|
||||
<a href="{% url 'horizon:project:volumes:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
|
@ -0,0 +1,25 @@
|
|||
{% load i18n horizon humanize %}
|
||||
|
||||
<h3>{% trans "Description" %}:</h3>
|
||||
|
||||
<p>{% trans "From here you can extend the size of a volume." %}</p>
|
||||
|
||||
<h3>{% trans "Volume Limits" %}</h3>
|
||||
|
||||
<div class="quota_title clearfix">
|
||||
<strong>{% trans "Total Gigabytes" %} <span>({{ usages.gigabytesUsed|intcomma }} {% trans "GB" %})</span></strong>
|
||||
<p>{{ usages.maxTotalVolumeGigabytes|quota:_("GB")|intcomma }}</p>
|
||||
</div>
|
||||
|
||||
<div id="quota_size" data-progress-indicator-for="id_new_size" data-quota-limit="{{ usages.maxTotalVolumeGigabytes }}" data-quota-used="{{ usages.gigabytesUsed }}" class="quota_bar">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
if(typeof horizon.Quota !== 'undefined') {
|
||||
horizon.Quota.init();
|
||||
} else {
|
||||
addHorizonLoadEvent(function() {
|
||||
horizon.Quota.init();
|
||||
});
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,11 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Extend Volume" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Extend Volume") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/volumes/_extend.html' %}
|
||||
{% endblock %}
|
|
@ -859,3 +859,53 @@ class VolumeViewTests(test.TestCase):
|
|||
args=[volume.id])
|
||||
res = self.client.post(url, formData)
|
||||
self.assertRedirectsNoFollow(res, VOLUME_INDEX_URL)
|
||||
|
||||
@test.create_stubs({cinder: ('volume_get',
|
||||
'volume_extend')})
|
||||
def test_extend_volume(self):
|
||||
volume = self.volumes.first()
|
||||
formData = {'name': u'A Volume I Am Making',
|
||||
'orig_size': volume.size,
|
||||
'new_size': 100}
|
||||
|
||||
cinder.volume_get(IsA(http.HttpRequest), volume.id).\
|
||||
AndReturn(self.volumes.first())
|
||||
|
||||
cinder.volume_extend(IsA(http.HttpRequest),
|
||||
volume.id,
|
||||
formData['new_size']).AndReturn(volume)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:project:volumes:extend',
|
||||
args=[volume.id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
redirect_url = reverse('horizon:project:volumes:index')
|
||||
self.assertRedirectsNoFollow(res, redirect_url)
|
||||
|
||||
@test.create_stubs({cinder: ('volume_get',),
|
||||
quotas: ('tenant_limit_usages',)})
|
||||
def test_extend_volume_with_wrong_size(self):
|
||||
volume = self.volumes.first()
|
||||
usage_limit = {'maxTotalVolumeGigabytes': 100,
|
||||
'gigabytesUsed': 20,
|
||||
'volumesUsed': len(self.volumes.list()),
|
||||
'maxTotalVolumes': 6}
|
||||
formData = {'name': u'A Volume I Am Making',
|
||||
'orig_size': volume.size,
|
||||
'new_size': 10}
|
||||
|
||||
cinder.volume_get(IsA(http.HttpRequest), volume.id).\
|
||||
AndReturn(self.volumes.first())
|
||||
quotas.tenant_limit_usages(IsA(http.HttpRequest)).\
|
||||
AndReturn(usage_limit)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:project:volumes:extend',
|
||||
args=[volume.id])
|
||||
res = self.client.post(url, formData)
|
||||
self.assertFormError(res, 'form', None,
|
||||
"New size for extend must be greater than "
|
||||
"current size.")
|
||||
|
|
|
@ -23,6 +23,9 @@ from openstack_dashboard.dashboards.project.volumes import views
|
|||
urlpatterns = patterns('openstack_dashboard.dashboards.project.volumes.views',
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^create/$', views.CreateView.as_view(), name='create'),
|
||||
url(r'^(?P<volume_id>[^/]+)/extend/$',
|
||||
views.ExtendView.as_view(),
|
||||
name='extend'),
|
||||
url(r'^(?P<volume_id>[^/]+)/attach/$',
|
||||
views.EditAttachmentsView.as_view(),
|
||||
name='attach'),
|
||||
|
|
|
@ -132,6 +132,41 @@ class CreateView(forms.ModalFormView):
|
|||
return context
|
||||
|
||||
|
||||
class ExtendView(forms.ModalFormView):
|
||||
form_class = project_forms.ExtendForm
|
||||
template_name = 'project/volumes/extend.html'
|
||||
success_url = reverse_lazy("horizon:project:volumes:index")
|
||||
|
||||
def get_object(self):
|
||||
if not hasattr(self, "_object"):
|
||||
volume_id = self.kwargs['volume_id']
|
||||
try:
|
||||
self._object = cinder.volume_get(self.request, volume_id)
|
||||
except Exception:
|
||||
self._object = None
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve volume information.'))
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ExtendView, self).get_context_data(**kwargs)
|
||||
context['volume'] = self.get_object()
|
||||
try:
|
||||
usages = quotas.tenant_limit_usages(self.request)
|
||||
usages['gigabytesUsed'] = (usages['gigabytesUsed']
|
||||
- context['volume'].size)
|
||||
context['usages'] = usages
|
||||
except Exception:
|
||||
exceptions.handle(self.request)
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
volume = self.get_object()
|
||||
return {'id': self.kwargs['volume_id'],
|
||||
'name': volume.display_name,
|
||||
'orig_size': volume.size}
|
||||
|
||||
|
||||
class CreateSnapshotView(forms.ModalFormView):
|
||||
form_class = project_forms.CreateSnapshotForm
|
||||
template_name = 'project/volumes/create_snapshot.html'
|
||||
|
|
Loading…
Reference in New Issue