From 41e3a94ae18a9219ff4ab10389f3a7799c9b6493 Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Fri, 7 Feb 2014 12:58:35 -0500 Subject: [PATCH] GlusterFS: Fix create/restore backup 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 | 181 ++++++++++++++++++++++++++++- cinder/volume/drivers/glusterfs.py | 43 ++++++- 2 files changed, 218 insertions(+), 6 deletions(-) diff --git a/cinder/tests/test_glusterfs.py b/cinder/tests/test_glusterfs.py index 266af6e99..94dbd8b1b 100644 --- a/cinder/tests/test_glusterfs.py +++ b/cinder/tests/test_glusterfs.py @@ -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()) diff --git a/cinder/volume/drivers/glusterfs.py b/cinder/volume/drivers/glusterfs.py index 0b15de387..01238d9d3 100644 --- a/cinder/volume/drivers/glusterfs.py +++ b/cinder/volume/drivers/glusterfs.py @@ -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) -- 2.45.2