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
This commit is contained in:
Eric Harney 2014-02-07 12:58:35 -05:00
parent 10108f4d2e
commit 41e3a94ae1
2 changed files with 218 additions and 6 deletions

View 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())

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