]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
GlusterFS: Fix create/restore backup
authorEric Harney <eharney@redhat.com>
Fri, 7 Feb 2014 17:58:35 +0000 (12:58 -0500)
committerEric Harney <eharney@redhat.com>
Mon, 10 Feb 2014 16:19:25 +0000 (11:19 -0500)
Allow create_backup for GlusterFS volumes, but
only when snapshots do not exist.  (For now.)

Restore is a no-op for the driver, so allow it.

Related-Bug: 1247743
Closes-Bug: 1275977

Change-Id: I50b8f6cac684c967c7374bb43247a396ce936157

cinder/tests/test_glusterfs.py
cinder/volume/drivers/glusterfs.py

index 266af6e993ef36617711e9ca7f78347f7ee100dc..94dbd8b1b363d9a0dc153819853d09959548fd29 100644 (file)
@@ -34,6 +34,7 @@ from cinder import test
 from cinder import units
 from cinder import utils
 from cinder.volume import configuration as conf
+from cinder.volume import driver as base_driver
 from cinder.volume.drivers import glusterfs
 
 
@@ -47,6 +48,17 @@ class DumbVolume(object):
         return self.fields[item]
 
 
+class FakeDb(object):
+    msg = "Tests are broken: mock this out."
+
+    def volume_get(self, *a, **kw):
+        raise Exception(self.msg)
+
+    def snapshot_get_all_for_volume(self, *a, **kw):
+        """Mock this if you want results from it."""
+        return []
+
+
 class GlusterFsDriverTestCase(test.TestCase):
     """Test case for GlusterFS driver."""
 
@@ -78,7 +90,8 @@ class GlusterFsDriverTestCase(test.TestCase):
 
         self.stubs = stubout.StubOutForTesting()
         self._driver =\
-            glusterfs.GlusterfsDriver(configuration=self._configuration)
+            glusterfs.GlusterfsDriver(configuration=self._configuration,
+                                      db=FakeDb())
         self._driver.shares = {}
 
     def tearDown(self):
@@ -1679,3 +1692,169 @@ class GlusterFsDriverTestCase(test.TestCase):
 
         self.assertEqual(drv._get_mount_point_base(),
                          self.TEST_MNT_POINT_BASE)
+
+    def test_backup_volume(self):
+        """Backup a volume with no snapshots."""
+
+        (mox, drv) = self._mox, self._driver
+
+        mox.StubOutWithMock(drv, '_qemu_img_info')
+        mox.StubOutWithMock(drv.db, 'volume_get')
+        mox.StubOutWithMock(base_driver.VolumeDriver, 'backup_volume')
+        mox.StubOutWithMock(drv, '_read_info_file')
+        mox.StubOutWithMock(drv, 'get_active_image_from_info')
+
+        ctxt = context.RequestContext('fake_user', 'fake_project')
+        volume = self._simple_volume()
+        backup = {'volume_id': volume['id']}
+
+        drv._read_info_file(IgnoreArg(), empty_if_missing=True).AndReturn({})
+        drv.get_active_image_from_info(IgnoreArg()).AndReturn('/some/path')
+
+        info = imageutils.QemuImgInfo()
+        info.file_format = 'raw'
+
+        drv.db.volume_get(ctxt, volume['id']).AndReturn(volume)
+        drv._qemu_img_info(IgnoreArg()).AndReturn(info)
+
+        base_driver.VolumeDriver.backup_volume(IgnoreArg(),
+                                               IgnoreArg(),
+                                               IgnoreArg())
+
+        mox.ReplayAll()
+
+        drv.backup_volume(ctxt, backup, IgnoreArg())
+
+    def test_backup_volume_previous_snap(self):
+        """Backup a volume that previously had a snapshot.
+
+           Snapshot was deleted, snap_info is different from above.
+        """
+
+        (mox, drv) = self._mox, self._driver
+
+        mox.StubOutWithMock(drv, '_qemu_img_info')
+        mox.StubOutWithMock(drv.db, 'volume_get')
+        mox.StubOutWithMock(drv, '_read_info_file')
+        mox.StubOutWithMock(drv, 'get_active_image_from_info')
+        mox.StubOutWithMock(base_driver.VolumeDriver, 'backup_volume')
+
+        ctxt = context.RequestContext('fake_user', 'fake_project')
+        volume = self._simple_volume()
+        backup = {'volume_id': volume['id']}
+
+        drv._read_info_file(IgnoreArg(), empty_if_missing=True).AndReturn(
+            {'active': 'file2'})
+        drv.get_active_image_from_info(IgnoreArg()).AndReturn('/some/file2')
+
+        info = imageutils.QemuImgInfo()
+        info.file_format = 'raw'
+
+        drv.db.volume_get(ctxt, volume['id']).AndReturn(volume)
+        drv._qemu_img_info(IgnoreArg()).AndReturn(info)
+
+        base_driver.VolumeDriver.backup_volume(IgnoreArg(),
+                                               IgnoreArg(),
+                                               IgnoreArg())
+
+        mox.ReplayAll()
+
+        drv.backup_volume(ctxt, backup, IgnoreArg())
+
+    def test_backup_snap_failure_1(self):
+        """Backup fails if snapshot exists (database)."""
+
+        (mox, drv) = self._mox, self._driver
+        mox.StubOutWithMock(drv.db, 'snapshot_get_all_for_volume')
+        mox.StubOutWithMock(base_driver.VolumeDriver, 'backup_volume')
+
+        ctxt = context.RequestContext('fake_user', 'fake_project')
+        volume = self._simple_volume()
+        backup = {'volume_id': volume['id']}
+
+        drv.db.snapshot_get_all_for_volume(ctxt, volume['id']).AndReturn(
+            [{'snap1': 'a'}, {'snap2': 'b'}])
+
+        base_driver.VolumeDriver.backup_volume(IgnoreArg(),
+                                               IgnoreArg(),
+                                               IgnoreArg())
+
+        mox.ReplayAll()
+
+        self.assertRaises(exception.InvalidVolume,
+                          drv.backup_volume,
+                          ctxt, backup, IgnoreArg())
+
+    def test_backup_snap_failure_2(self):
+        """Backup fails if snapshot exists (on-disk)."""
+
+        (mox, drv) = self._mox, self._driver
+        mox.StubOutWithMock(drv, '_read_info_file')
+        mox.StubOutWithMock(drv.db, 'volume_get')
+        mox.StubOutWithMock(drv, 'get_active_image_from_info')
+        mox.StubOutWithMock(drv, '_qemu_img_info')
+        mox.StubOutWithMock(base_driver.VolumeDriver, 'backup_volume')
+
+        ctxt = context.RequestContext('fake_user', 'fake_project')
+        volume = self._simple_volume()
+        backup = {'volume_id': volume['id']}
+
+        drv.db.volume_get(ctxt, volume['id']).AndReturn(volume)
+
+        drv._read_info_file(IgnoreArg(), empty_if_missing=True).AndReturn(
+            {'id1': 'file1',
+             'id2': 'file2',
+             'active': 'file2'})
+
+        drv.get_active_image_from_info(IgnoreArg()).\
+            AndReturn('/some/path/file2')
+
+        info = imageutils.QemuImgInfo()
+        info.file_format = 'raw'
+        info.backing_file = 'file1'
+
+        drv._qemu_img_info(IgnoreArg()).AndReturn(info)
+
+        base_driver.VolumeDriver.backup_volume(IgnoreArg(),
+                                               IgnoreArg(),
+                                               IgnoreArg())
+
+        mox.ReplayAll()
+
+        self.assertRaises(exception.InvalidVolume,
+                          drv.backup_volume,
+                          ctxt, backup, IgnoreArg())
+
+    def test_backup_failure_unsupported_format(self):
+        """Attempt to backup a volume with a qcow2 base."""
+
+        (mox, drv) = self._mox, self._driver
+
+        mox.StubOutWithMock(drv, '_qemu_img_info')
+        mox.StubOutWithMock(drv.db, 'volume_get')
+        mox.StubOutWithMock(base_driver.VolumeDriver, 'backup_volume')
+        mox.StubOutWithMock(drv, '_read_info_file')
+        mox.StubOutWithMock(drv, 'get_active_image_from_info')
+
+        ctxt = context.RequestContext('fake_user', 'fake_project')
+        volume = self._simple_volume()
+        backup = {'volume_id': volume['id']}
+
+        drv._read_info_file(IgnoreArg(), empty_if_missing=True).AndReturn({})
+        drv.get_active_image_from_info(IgnoreArg()).AndReturn('/some/path')
+
+        info = imageutils.QemuImgInfo()
+        info.file_format = 'qcow2'
+
+        drv.db.volume_get(ctxt, volume['id']).AndReturn(volume)
+        drv._qemu_img_info(IgnoreArg()).AndReturn(info)
+
+        base_driver.VolumeDriver.backup_volume(IgnoreArg(),
+                                               IgnoreArg(),
+                                               IgnoreArg())
+
+        mox.ReplayAll()
+
+        self.assertRaises(exception.InvalidVolume,
+                          drv.backup_volume,
+                          ctxt, backup, IgnoreArg())
index 0b15de387cc843c9caf4d2575591407adb26d65a..01238d9d3adfd7362861ee47837f6a104e0e6f9f 100644 (file)
@@ -1147,9 +1147,42 @@ class GlusterfsDriver(nfs.RemoteFsDriver):
         return self.base
 
     def backup_volume(self, context, backup, backup_service):
-        """Create a new backup from an existing volume."""
-        raise NotImplementedError()
+        """Create a new backup from an existing volume.
 
-    def restore_backup(self, context, backup, volume, backup_service):
-        """Restore an existing backup to a new or existing volume."""
-        raise NotImplementedError()
+        Allow a backup to occur only if no snapshots exist.
+        Check both Cinder and the file on-disk.  The latter is only
+        a safety mechanism to prevent further damage if the snapshot
+        information is already inconsistent.
+        """
+
+        snapshots = self.db.snapshot_get_all_for_volume(context,
+                                                        backup['volume_id'])
+        snap_error_msg = _('Backup is not supported for GlusterFS '
+                           'volumes with snapshots.')
+        if len(snapshots) > 0:
+            raise exception.InvalidVolume(snap_error_msg)
+
+        volume = self.db.volume_get(context, backup['volume_id'])
+
+        volume_dir = self._local_volume_dir(volume)
+        active_file_path = os.path.join(
+            volume_dir,
+            self.get_active_image_from_info(volume))
+
+        info = self._qemu_img_info(active_file_path)
+
+        if info.backing_file is not None:
+            msg = _('No snapshots found in database, but '
+                    '%(path)s has backing file '
+                    '%(backing_file)s!') % {'path': active_file_path,
+                                            'backing_file': info.backing_file}
+            LOG.error(msg)
+            raise exception.InvalidVolume(snap_error_msg)
+
+        if info.file_format != 'raw':
+            msg = _('Backup is only supported for raw-formatted '
+                    'GlusterFS volumes.')
+            raise exception.InvalidVolume(msg)
+
+        return super(GlusterfsDriver, self).backup_volume(
+            context, backup, backup_service)