summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2012-03-30 16:38:27 +0000
committerGerrit Code Review <review@openstack.org>2012-03-30 16:38:27 +0000
commitad4e65a6688fceecac3f44f9d8eb46e35b6ed6d8 (patch)
tree073fe7b615a5adeaf82c81adbf18aa1d11fc8ab5
parent0a6ffe3f2a570aa222814efa149882edf8a7b224 (diff)
parent43c63d11417de8624d120ca78a9849d09ffa8cf6 (diff)
downloadnova-ad4e65a6688fceecac3f44f9d8eb46e35b6ed6d8.tar.gz
nova-ad4e65a6688fceecac3f44f9d8eb46e35b6ed6d8.tar.xz
nova-ad4e65a6688fceecac3f44f9d8eb46e35b6ed6d8.zip
Merge "Check that volume has no snapshots before deletion"
-rw-r--r--nova/db/api.py5
-rw-r--r--nova/db/sqlalchemy/api.py7
-rw-r--r--nova/tests/test_volume.py23
-rw-r--r--nova/volume/api.py6
4 files changed, 41 insertions, 0 deletions
diff --git a/nova/db/api.py b/nova/db/api.py
index 850c772c7..a7fbd34fd 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -1053,6 +1053,11 @@ def snapshot_get_all_by_project(context, project_id):
return IMPL.snapshot_get_all_by_project(context, project_id)
+def snapshot_get_all_for_volume(context, volume_id):
+ """Get all snapshots for a volume."""
+ return IMPL.snapshot_get_all_for_volume(context, volume_id)
+
+
def snapshot_update(context, snapshot_id, values):
"""Set the given properties on an snapshot and update it.
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 040534a0d..2ce42e1cc 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -2658,6 +2658,13 @@ def snapshot_get_all(context):
@require_context
+def snapshot_get_all_for_volume(context, volume_id):
+ return model_query(context, models.Snapshot, read_deleted='no',
+ project_only=True).\
+ filter_by(volume_id=volume_id).all()
+
+
+@require_context
def snapshot_get_all_by_project(context, project_id):
authorize_project_context(context, project_id)
return model_query(context, models.Snapshot).\
diff --git a/nova/tests/test_volume.py b/nova/tests/test_volume.py
index caeaa7098..be589d3e2 100644
--- a/nova/tests/test_volume.py
+++ b/nova/tests/test_volume.py
@@ -256,6 +256,29 @@ class VolumeTestCase(test.TestCase):
snapshot_id)
self.volume.delete_volume(self.context, volume['id'])
+ def test_cant_delete_volume_with_snapshots(self):
+ """Test snapshot can be created and deleted."""
+ volume = self._create_volume()
+ self.volume.create_volume(self.context, volume['id'])
+ snapshot_id = self._create_snapshot(volume['id'])
+ self.volume.create_snapshot(self.context, volume['id'], snapshot_id)
+ self.assertEqual(snapshot_id,
+ db.snapshot_get(context.get_admin_context(),
+ snapshot_id).id)
+
+ volume['status'] = 'available'
+ volume['host'] = 'fakehost'
+
+ volume_api = nova.volume.api.API()
+
+ self.assertRaises(exception.InvalidVolume,
+ volume_api.delete,
+ self.context,
+ volume)
+
+ self.volume.delete_snapshot(self.context, snapshot_id)
+ self.volume.delete_volume(self.context, volume['id'])
+
def test_create_snapshot_force(self):
"""Test snapshot in use can be created forcibly."""
diff --git a/nova/volume/api.py b/nova/volume/api.py
index bf79fd119..26d56c057 100644
--- a/nova/volume/api.py
+++ b/nova/volume/api.py
@@ -136,6 +136,12 @@ class API(base.Base):
if volume['status'] not in ["available", "error"]:
msg = _("Volume status must be available or error")
raise exception.InvalidVolume(reason=msg)
+
+ snapshots = self.db.snapshot_get_all_for_volume(context, volume_id)
+ if len(snapshots):
+ msg = _("Volume still has %d dependent snapshots" % len(snapshots))
+ raise exception.InvalidVolume(reason=msg)
+
now = utils.utcnow()
self.db.volume_update(context, volume_id, {'status': 'deleting',
'terminated_at': now})