Allowing snapshoting attached volume
The cinder api allows calling snapshot on attached volumes, we should enable it on horizon. Change-Id: I33d7bbca0bf993ace7c243867aa5f57438a57a5d Fixes: bug #1202929
This commit is contained in:
parent
d8b8284c7b
commit
04494e8678
|
@ -116,9 +116,11 @@ def volume_snapshot_list(request):
|
||||||
return c_client.volume_snapshots.list()
|
return c_client.volume_snapshots.list()
|
||||||
|
|
||||||
|
|
||||||
def volume_snapshot_create(request, volume_id, name, description):
|
def volume_snapshot_create(request, volume_id, name,
|
||||||
|
description=None, force=False):
|
||||||
return cinderclient(request).volume_snapshots.create(
|
return cinderclient(request).volume_snapshots.create(
|
||||||
volume_id, display_name=name, display_description=description)
|
volume_id, force=force, display_name=name,
|
||||||
|
display_description=description)
|
||||||
|
|
||||||
|
|
||||||
def volume_snapshot_delete(request, snapshot_id):
|
def volume_snapshot_delete(request, snapshot_id):
|
||||||
|
|
|
@ -32,9 +32,12 @@ INDEX_URL = reverse('horizon:project:images_and_snapshots:index')
|
||||||
|
|
||||||
|
|
||||||
class VolumeSnapshotsViewTests(test.TestCase):
|
class VolumeSnapshotsViewTests(test.TestCase):
|
||||||
@test.create_stubs({quotas: ('tenant_limit_usages',)})
|
@test.create_stubs({cinder: ('volume_get',),
|
||||||
|
quotas: ('tenant_limit_usages',)})
|
||||||
def test_create_snapshot_get(self):
|
def test_create_snapshot_get(self):
|
||||||
volume = self.volumes.first()
|
volume = self.volumes.first()
|
||||||
|
cinder.volume_get(IsA(http.HttpRequest), volume.id) \
|
||||||
|
.AndReturn(volume)
|
||||||
usage_limit = {'maxTotalVolumeGigabytes': 250,
|
usage_limit = {'maxTotalVolumeGigabytes': 250,
|
||||||
'gigabytesUsed': 20,
|
'gigabytesUsed': 20,
|
||||||
'volumesUsed': len(self.volumes.list()),
|
'volumesUsed': len(self.volumes.list()),
|
||||||
|
@ -49,15 +52,45 @@ class VolumeSnapshotsViewTests(test.TestCase):
|
||||||
|
|
||||||
self.assertTemplateUsed(res, 'project/volumes/create_snapshot.html')
|
self.assertTemplateUsed(res, 'project/volumes/create_snapshot.html')
|
||||||
|
|
||||||
@test.create_stubs({cinder: ('volume_snapshot_create',)})
|
@test.create_stubs({cinder: ('volume_get',
|
||||||
|
'volume_snapshot_create',)})
|
||||||
def test_create_snapshot_post(self):
|
def test_create_snapshot_post(self):
|
||||||
volume = self.volumes.first()
|
volume = self.volumes.first()
|
||||||
snapshot = self.volume_snapshots.first()
|
snapshot = self.volume_snapshots.first()
|
||||||
|
|
||||||
|
cinder.volume_get(IsA(http.HttpRequest), volume.id) \
|
||||||
|
.AndReturn(volume)
|
||||||
cinder.volume_snapshot_create(IsA(http.HttpRequest),
|
cinder.volume_snapshot_create(IsA(http.HttpRequest),
|
||||||
volume.id,
|
volume.id,
|
||||||
snapshot.display_name,
|
snapshot.display_name,
|
||||||
snapshot.display_description) \
|
snapshot.display_description,
|
||||||
|
force=False) \
|
||||||
|
.AndReturn(snapshot)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
formData = {'method': 'CreateSnapshotForm',
|
||||||
|
'tenant_id': self.tenant.id,
|
||||||
|
'volume_id': volume.id,
|
||||||
|
'name': snapshot.display_name,
|
||||||
|
'description': snapshot.display_description}
|
||||||
|
url = reverse('horizon:project:volumes:create_snapshot',
|
||||||
|
args=[volume.id])
|
||||||
|
res = self.client.post(url, formData)
|
||||||
|
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||||
|
|
||||||
|
@test.create_stubs({cinder: ('volume_get',
|
||||||
|
'volume_snapshot_create',)})
|
||||||
|
def test_force_create_snapshot(self):
|
||||||
|
volume = self.volumes.get(name='my_volume')
|
||||||
|
snapshot = self.volume_snapshots.first()
|
||||||
|
|
||||||
|
cinder.volume_get(IsA(http.HttpRequest), volume.id) \
|
||||||
|
.AndReturn(volume)
|
||||||
|
cinder.volume_snapshot_create(IsA(http.HttpRequest),
|
||||||
|
volume.id,
|
||||||
|
snapshot.display_name,
|
||||||
|
snapshot.display_description,
|
||||||
|
force=True) \
|
||||||
.AndReturn(snapshot)
|
.AndReturn(snapshot)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
|
|
@ -379,12 +379,20 @@ class CreateSnapshotForm(forms.SelfHandlingForm):
|
||||||
|
|
||||||
def handle(self, request, data):
|
def handle(self, request, data):
|
||||||
try:
|
try:
|
||||||
|
volume = cinder.volume_get(request,
|
||||||
|
data['volume_id'])
|
||||||
|
force = False
|
||||||
|
message = _('Creating volume snapshot "%s".') % data['name']
|
||||||
|
if volume.status == 'in-use':
|
||||||
|
force = True
|
||||||
|
message = _('Forcing to create snapshot "%s" '
|
||||||
|
'from attached volume.') % data['name']
|
||||||
snapshot = cinder.volume_snapshot_create(request,
|
snapshot = cinder.volume_snapshot_create(request,
|
||||||
data['volume_id'],
|
data['volume_id'],
|
||||||
data['name'],
|
data['name'],
|
||||||
data['description'])
|
data['description'],
|
||||||
|
force=force)
|
||||||
|
|
||||||
message = _('Creating volume snapshot "%s"') % data['name']
|
|
||||||
messages.info(request, message)
|
messages.info(request, message)
|
||||||
return snapshot
|
return snapshot
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
@ -94,7 +94,7 @@ class CreateSnapshot(tables.LinkAction):
|
||||||
classes = ("ajax-modal", "btn-camera")
|
classes = ("ajax-modal", "btn-camera")
|
||||||
|
|
||||||
def allowed(self, request, volume=None):
|
def allowed(self, request, volume=None):
|
||||||
return volume.status == "available"
|
return volume.status in ("available", "in-use")
|
||||||
|
|
||||||
|
|
||||||
class UpdateRow(tables.Row):
|
class UpdateRow(tables.Row):
|
||||||
|
|
|
@ -20,6 +20,10 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block modal-footer %}
|
{% block modal-footer %}
|
||||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Volume Snapshot" %}" />
|
{% if attached %}
|
||||||
|
<input class="btn btn-primary btn-warning pull-right" type="submit" value="{% trans "Create Volume Snapshot (Force)" %}" />
|
||||||
|
{% else %}
|
||||||
|
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Volume Snapshot" %}" />
|
||||||
|
{% endif %}
|
||||||
<a href="{% url 'horizon:project:volumes:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
<a href="{% url 'horizon:project:volumes:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -141,9 +141,19 @@ class CreateSnapshotView(forms.ModalFormView):
|
||||||
context = super(CreateSnapshotView, self).get_context_data(**kwargs)
|
context = super(CreateSnapshotView, self).get_context_data(**kwargs)
|
||||||
context['volume_id'] = self.kwargs['volume_id']
|
context['volume_id'] = self.kwargs['volume_id']
|
||||||
try:
|
try:
|
||||||
|
volume = cinder.volume_get(self.request, context['volume_id'])
|
||||||
|
if (volume.status == 'in-use'):
|
||||||
|
context['attached'] = True
|
||||||
|
context['form'].set_warning(_("This volume is currently "
|
||||||
|
"attached to an instance. "
|
||||||
|
"In some cases, creating a "
|
||||||
|
"snapshot from an attached "
|
||||||
|
"volume can result in a "
|
||||||
|
"corrupted snapshot."))
|
||||||
context['usages'] = quotas.tenant_limit_usages(self.request)
|
context['usages'] = quotas.tenant_limit_usages(self.request)
|
||||||
except Exception:
|
except Exception:
|
||||||
exceptions.handle(self.request)
|
exceptions.handle(self.request,
|
||||||
|
_('Unable to retrieve volume information.'))
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
|
|
Loading…
Reference in New Issue