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):