diff --git a/cinder/backup/drivers/ceph.py b/cinder/backup/drivers/ceph.py index ff390a4f4..2a644dd15 100644 --- a/cinder/backup/drivers/ceph.py +++ b/cinder/backup/drivers/ceph.py @@ -88,6 +88,9 @@ service_opts = [ help='RBD stripe unit to use when creating a backup image.'), cfg.IntOpt('backup_ceph_stripe_count', default=0, help='RBD stripe count to use when creating a backup image.'), + cfg.BoolOpt('backup_ceph_image_journals', default=False, + help='If True, apply JOURNALING and EXCLUSIVE_LOCK feature ' + 'bits to the backup RBD objects to allow mirroring'), cfg.BoolOpt('restore_discard_excess_bytes', default=True, help='If True, always discard excess bytes when restoring ' 'volumes i.e. pad with zeroes.') @@ -222,6 +225,16 @@ class CephBackupDriver(driver.BackupDriver): """Determine if striping is supported by our version of librbd.""" return hasattr(self.rbd, 'RBD_FEATURE_STRIPINGV2') + @property + def _supports_exclusive_lock(self): + """Determine if exclusive-lock is supported by librbd.""" + return hasattr(self.rbd, 'RBD_FEATURE_EXCLUSIVE_LOCK') + + @property + def _supports_journaling(self): + """Determine if journaling is supported by our version of librbd.""" + return hasattr(self.rbd, 'RBD_FEATURE_JOURNALING') + def _get_rbd_support(self): """Determine RBD features supported by our version of librbd.""" old_format = True @@ -233,6 +246,25 @@ class CephBackupDriver(driver.BackupDriver): old_format = False features |= self.rbd.RBD_FEATURE_STRIPINGV2 + # journaling requires exclusive_lock; check both together + if CONF.backup_ceph_image_journals: + if self._supports_exclusive_lock and self._supports_journaling: + old_format = False + features |= (self.rbd.RBD_FEATURE_EXCLUSIVE_LOCK | + self.rbd.RBD_FEATURE_JOURNALING) + else: + # FIXME (tasker): when the backup manager supports loading the + # driver during its initialization, this exception should be + # moved to the driver's initialization so that it can stop + # the service from starting when the underyling RBD does not + # support the requested features. + LOG.error(_LE("RBD journaling not supported - unable to " + "support per image mirroring in backup pool")) + raise exception.BackupInvalidCephArgs( + _("Image Journaling set but RBD backend does " + "not support journaling") + ) + return (old_format, features) def _connect_to_rados(self, pool=None): diff --git a/cinder/tests/unit/backup/drivers/test_backup_ceph.py b/cinder/tests/unit/backup/drivers/test_backup_ceph.py index 918cc39c7..de0e7cdd4 100644 --- a/cinder/tests/unit/backup/drivers/test_backup_ceph.py +++ b/cinder/tests/unit/backup/drivers/test_backup_ceph.py @@ -22,6 +22,7 @@ import uuid import mock from os_brick.initiator import linuxrbd from oslo_concurrency import processutils +from oslo_config import cfg from oslo_serialization import jsonutils from oslo_utils import units import six @@ -42,6 +43,8 @@ from cinder.tests.unit import fake_constants as fake # NOTE: this must be initialised in test setUp(). RAISED_EXCEPTIONS = [] +CONF = cfg.CONF + class MockException(Exception): @@ -202,8 +205,13 @@ class BackupCephTestCase(test.TestCase): def test_get_rbd_support(self): del self.service.rbd.RBD_FEATURE_LAYERING del self.service.rbd.RBD_FEATURE_STRIPINGV2 + del self.service.rbd.RBD_FEATURE_EXCLUSIVE_LOCK + del self.service.rbd.RBD_FEATURE_JOURNALING self.assertFalse(hasattr(self.service.rbd, 'RBD_FEATURE_LAYERING')) self.assertFalse(hasattr(self.service.rbd, 'RBD_FEATURE_STRIPINGV2')) + self.assertFalse(hasattr(self.service.rbd, + 'RBD_FEATURE_EXCLUSIVE_LOCK')) + self.assertFalse(hasattr(self.service.rbd, 'RBD_FEATURE_JOURNALING')) oldformat, features = self.service._get_rbd_support() self.assertTrue(oldformat) @@ -221,6 +229,28 @@ class BackupCephTestCase(test.TestCase): self.assertFalse(oldformat) self.assertEqual(1 | 2, features) + # initially, backup_ceph_image_journals = False. test that + # the flags are defined, but that they are not returned. + self.service.rbd.RBD_FEATURE_EXCLUSIVE_LOCK = 4 + + oldformat, features = self.service._get_rbd_support() + self.assertFalse(oldformat) + self.assertEqual(1 | 2, features) + + self.service.rbd.RBD_FEATURE_JOURNALING = 64 + + oldformat, features = self.service._get_rbd_support() + self.assertFalse(oldformat) + self.assertEqual(1 | 2, features) + + # test that the config setting properly sets the FEATURE bits. + # because journaling requires exclusive-lock, these are set + # at the same time. + CONF.set_override("backup_ceph_image_journals", True) + oldformat, features = self.service._get_rbd_support() + self.assertFalse(oldformat) + self.assertEqual(1 | 2 | 4 | 64, features) + @common_mocks def test_get_most_recent_snap(self): last = 'backup.%s.snap.9824923.1212' % (uuid.uuid4()) diff --git a/releasenotes/notes/backup-ceph-driver-journaling-exculsive-lock-features-6b6044138a288a83.yaml b/releasenotes/notes/backup-ceph-driver-journaling-exculsive-lock-features-6b6044138a288a83.yaml new file mode 100644 index 000000000..d62a51dac --- /dev/null +++ b/releasenotes/notes/backup-ceph-driver-journaling-exculsive-lock-features-6b6044138a288a83.yaml @@ -0,0 +1,4 @@ +--- +features: + - Added new BoolOpt ``backup_ceph_image_journals`` for enabling the Ceph + image features required to support RBD mirroring of Cinder backup pool.