diff --git a/openstack_dashboard/api/cinder.py b/openstack_dashboard/api/cinder.py
index 62634cbb3f..432b1ac5bf 100644
--- a/openstack_dashboard/api/cinder.py
+++ b/openstack_dashboard/api/cinder.py
@@ -116,9 +116,11 @@ def volume_snapshot_list(request):
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(
- 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):
diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tests.py b/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tests.py
index 5f1d1e6ddf..12c21f1ebe 100644
--- a/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tests.py
+++ b/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tests.py
@@ -32,9 +32,12 @@ INDEX_URL = reverse('horizon:project:images_and_snapshots:index')
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):
volume = self.volumes.first()
+ cinder.volume_get(IsA(http.HttpRequest), volume.id) \
+ .AndReturn(volume)
usage_limit = {'maxTotalVolumeGigabytes': 250,
'gigabytesUsed': 20,
'volumesUsed': len(self.volumes.list()),
@@ -49,15 +52,45 @@ class VolumeSnapshotsViewTests(test.TestCase):
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):
volume = self.volumes.first()
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) \
+ 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)
self.mox.ReplayAll()
diff --git a/openstack_dashboard/dashboards/project/volumes/forms.py b/openstack_dashboard/dashboards/project/volumes/forms.py
index d91879bdb1..6c60c0928b 100644
--- a/openstack_dashboard/dashboards/project/volumes/forms.py
+++ b/openstack_dashboard/dashboards/project/volumes/forms.py
@@ -379,12 +379,20 @@ class CreateSnapshotForm(forms.SelfHandlingForm):
def handle(self, request, data):
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,
data['volume_id'],
data['name'],
- data['description'])
+ data['description'],
+ force=force)
- message = _('Creating volume snapshot "%s"') % data['name']
messages.info(request, message)
return snapshot
except Exception:
diff --git a/openstack_dashboard/dashboards/project/volumes/tables.py b/openstack_dashboard/dashboards/project/volumes/tables.py
index 51bd06ea2d..9238440ea3 100644
--- a/openstack_dashboard/dashboards/project/volumes/tables.py
+++ b/openstack_dashboard/dashboards/project/volumes/tables.py
@@ -94,7 +94,7 @@ class CreateSnapshot(tables.LinkAction):
classes = ("ajax-modal", "btn-camera")
def allowed(self, request, volume=None):
- return volume.status == "available"
+ return volume.status in ("available", "in-use")
class UpdateRow(tables.Row):
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_create_snapshot.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_create_snapshot.html
index 305b509a45..c99a8792a2 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_create_snapshot.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_create_snapshot.html
@@ -20,6 +20,10 @@
{% endblock %}
{% block modal-footer %}
-
+ {% if attached %}
+
+ {% else %}
+
+ {% endif %}
{% trans "Cancel" %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/views.py b/openstack_dashboard/dashboards/project/volumes/views.py
index a02b4f7a78..be4df9015f 100644
--- a/openstack_dashboard/dashboards/project/volumes/views.py
+++ b/openstack_dashboard/dashboards/project/volumes/views.py
@@ -141,9 +141,19 @@ class CreateSnapshotView(forms.ModalFormView):
context = super(CreateSnapshotView, self).get_context_data(**kwargs)
context['volume_id'] = self.kwargs['volume_id']
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)
except Exception:
- exceptions.handle(self.request)
+ exceptions.handle(self.request,
+ _('Unable to retrieve volume information.'))
return context
def get_initial(self):