From bcdec128e4f96b2d931e6a67cf6401eff5bb1cd4 Mon Sep 17 00:00:00 2001 From: Dmitriy Rabotyagov Date: Thu, 15 May 2025 18:27:41 +0200 Subject: [PATCH] Introduce mariadb_backup backup_strategy With mariabackup being renamed to mariadb-backup binary, to keep some backwards compatability instead of renaming the binary to call, this patch introduces the whole new strategy designed to pretty much override only used binary name for modern MariaDB versions. This new strategy is selected whenever requested datastore version is equal or higher 10.4. This patch is alternative to [1] [1] https://review.opendev.org/c/openstack/trove/+/948643 Change-Id: I5336c8f98d9f1f33810113b9d7a84f1bef3deb66 --- backup/drivers/mariabackup.py | 23 ++++- backup/main.py | 24 ++++- .../tests/unittests/drivers/test_mariadb.py | 95 +++++++++++++++++++ .../mariadb-backup-b93c334f06498ff4.yaml | 6 ++ 4 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/mariadb-backup-b93c334f06498ff4.yaml diff --git a/backup/drivers/mariabackup.py b/backup/drivers/mariabackup.py index 0da4b847ba..9e0a5a4e7c 100644 --- a/backup/drivers/mariabackup.py +++ b/backup/drivers/mariabackup.py @@ -25,17 +25,19 @@ CONF = cfg.CONF class MariaBackup(mysql_base.MySQLBaseRunner): """Implementation of Backup and Restore using mariabackup.""" + backup_binary = 'mariabackup' restore_cmd = ('mbstream -x -C %(restore_location)s') - prepare_cmd = 'mariabackup --prepare --target-dir=%(restore_location)s' + prepare_cmd = \ + f'{backup_binary} --prepare --target-dir=%(restore_location)s' def __init__(self, *args, **kwargs): super(MariaBackup, self).__init__(*args, **kwargs) - self.backup_log = '/tmp/mariabackup.log' + self.backup_log = f'/tmp/{self.backup_binary}.log' self._gzip = True @property def cmd(self): - cmd = ('mariabackup --backup --stream=xbstream ' + + cmd = (f'{self.backup_binary} --backup --stream=xbstream ' + self.user_and_pass) return cmd @@ -64,9 +66,15 @@ class MariaBackup(mysql_base.MySQLBaseRunner): raise Exception(msg) +class MariaDBBackup(MariaBackup): + """Implementation of Backup and Restore using mariadb-backup.""" + backup_binary = 'mariadb-backup' + + class MariaBackupIncremental(MariaBackup): """Incremental backup and restore using mariabackup.""" - incremental_prep = ('mariabackup --prepare ' + incremental_prep_binary = MariaBackup.backup_binary + incremental_prep = (f'{incremental_prep_binary} --prepare ' '--target-dir=%(restore_location)s ' '%(incremental_args)s') @@ -81,7 +89,7 @@ class MariaBackupIncremental(MariaBackup): @property def cmd(self): cmd = ( - 'mariabackup --backup --stream=xbstream' + f'{self.incremental_prep_binary} --backup --stream=xbstream' ' --incremental-lsn=%(lsn)s ' + self.user_and_pass ) @@ -102,3 +110,8 @@ class MariaBackupIncremental(MariaBackup): LOG.info('Running incremental restore') self.incremental_restore(self.location, self.checksum) return self.restore_content_length + + +class MariaDBBackupIncremental(MariaBackupIncremental): + """Incremental backup and restore using mariadb-backup.""" + incremental_prep_binary = MariaDBBackup.backup_binary diff --git a/backup/main.py b/backup/main.py index 64fa47bae9..fa9dd02163 100644 --- a/backup/main.py +++ b/backup/main.py @@ -19,6 +19,7 @@ import sys from oslo_config import cfg from oslo_log import log as logging from oslo_utils import importutils +from semantic_version import Version topdir = os.path.normpath( os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir)) @@ -37,7 +38,13 @@ cli_opts = [ cfg.StrOpt( 'driver', default='innobackupex', - choices=['innobackupex', 'mariabackup', 'pg_basebackup', 'xtrabackup'] + choices=[ + 'innobackupex', + 'mariabackup', + 'mariadb_backup', + 'pg_basebackup', + 'xtrabackup' + ] ), cfg.BoolOpt('backup'), cfg.StrOpt( @@ -72,7 +79,10 @@ driver_mapping = { 'innobackupex': 'backup.drivers.innobackupex.InnoBackupEx', 'innobackupex_inc': 'backup.drivers.innobackupex.InnoBackupExIncremental', 'mariabackup': 'backup.drivers.mariabackup.MariaBackup', + 'mariadb_backup': 'backup.drivers.mariabackup.MariaDBBackup', 'mariabackup_inc': 'backup.drivers.mariabackup.MariaBackupIncremental', + 'mariadb_backup_inc': + 'backup.drivers.mariabackup.MariaDBBackupIncremental', 'pg_basebackup': 'backup.drivers.postgres.PgBasebackup', 'pg_basebackup_inc': 'backup.drivers.postgres.PgBasebackupIncremental', 'xtrabackup': 'backup.drivers.xtrabackup.XtraBackup', @@ -148,13 +158,19 @@ def main(): CONF(sys.argv[1:], project='trove-backup') logging.setup(CONF, 'trove-backup') - runner_cls = importutils.import_class(driver_mapping[CONF.driver]) + driver = CONF.driver + if driver == "mariabackup": + ds_version = CONF.swift_extra_metadata.get('datastore_version', '0.0') + if Version.coerce(ds_version) >= Version.coerce("10.4"): + driver = "mariadb_backup" + + runner_cls = importutils.import_class(driver_mapping[driver]) storage = importutils.import_class(storage_mapping[CONF.storage_driver])() if CONF.backup: if CONF.incremental: runner_cls = importutils.import_class( - driver_mapping['%s_inc' % CONF.driver]) + driver_mapping['%s_inc' % driver]) LOG.info('Starting backup database to %s, backup ID %s', CONF.storage_driver, CONF.backup_id) @@ -163,7 +179,7 @@ def main(): if storage.is_incremental_backup(CONF.restore_from): LOG.debug('Restore from incremental backup') runner_cls = importutils.import_class( - driver_mapping['%s_inc' % CONF.driver]) + driver_mapping['%s_inc' % driver]) LOG.info('Starting restore database from %s, location: %s', CONF.storage_driver, CONF.restore_from) diff --git a/backup/tests/unittests/drivers/test_mariadb.py b/backup/tests/unittests/drivers/test_mariadb.py index 0ec70d3077..b9f9a82b5c 100644 --- a/backup/tests/unittests/drivers/test_mariadb.py +++ b/backup/tests/unittests/drivers/test_mariadb.py @@ -31,7 +31,10 @@ driver_mapping = { 'innobackupex': 'backup.drivers.innobackupex.InnoBackupEx', 'innobackupex_inc': 'backup.drivers.innobackupex.InnoBackupExIncremental', 'mariabackup': 'backup.drivers.mariabackup.MariaBackup', + 'mariadb_backup': 'backup.drivers.mariabackup.MariaDBBackup', 'mariabackup_inc': 'backup.drivers.mariabackup.MariaBackupIncremental', + 'mariadb_backup_inc': + 'backup.drivers.mariabackup.MariaDBBackupIncremental', 'pg_basebackup': 'backup.drivers.postgres.PgBasebackup', 'pg_basebackup_inc': 'backup.drivers.postgres.PgBasebackupIncremental', 'xtrabackup': 'backup.drivers.xtrabackup.XtraBackup', @@ -80,6 +83,47 @@ class TestMariaBackup(unittest.TestCase): self.assertEqual(runner.check_restore_process(), True) +class TestMariaDBBackup(unittest.TestCase): + def setUp(self): + self.runner_cls = importutils.import_class( + driver_mapping['mariadb_backup']) + self.params = {} + + # assertions + self.assertIsNotNone(self.runner_cls) + + def tearDown(self): + pass + + def test_instance(self): + '''Check instance''' + # call the method + runner = self.runner_cls(**self.params) + + # assertions + self.assertIsNotNone(runner) + + def test_cmd(self): + '''Check cmd property''' + # call the method + runner = self.runner_cls(**self.params) + + # assertions + cmd = ("mariadb-backup --backup --stream=xbstream {}".format( + runner.user_and_pass)) + self.assertEqual(runner.cmd, cmd) + + def test_check_restore_process(self): + '''Check manifest''' + runner = self.runner_cls(**self.params) + runner.process = MagicMock() + returncode = PropertyMock(return_value=0) + type(runner.process).returncode = returncode + + # call the method + self.assertEqual(runner.check_restore_process(), True) + + class TestMariaBackupIncremental(unittest.TestCase): def setUp(self): self.runner_cls = importutils.import_class( @@ -131,5 +175,56 @@ class TestMariaBackupIncremental(unittest.TestCase): self.assertEqual(ret, length) +class TestMariaDBBackupIncremental(unittest.TestCase): + def setUp(self): + self.runner_cls = importutils.import_class( + driver_mapping['mariadb_backup_inc']) + self.params = { + 'lsn': '1234567890', + 'incremental_dir': './' + } + self.metadata = {} + + def tearDown(self): + pass + + def test_cmd(self): + '''Check cmd property''' + # call the method + runner = self.runner_cls(**self.params) + + # assertions + cmd = ( + 'mariadb-backup --backup --stream=xbstream' + ' --incremental-lsn=%(lsn)s ' + + runner.user_and_pass + ) + self.assertEqual(runner.cmd, cmd) + + def test_get_metadata(self): + # prepare the test + runner = self.runner_cls(**self.params) + runner.get_metadata = MagicMock(return_value=self.metadata) + + # call the method + ret = runner.get_metadata() + + # assertions + self.assertEqual(ret, self.metadata) + + def test_run_restore(self): + # prepare the test + runner = self.runner_cls(**self.params) + length = 10 + runner.incremental_restore = MagicMock(return_value=length) + runner.restore_content_length = length + + # call the method + ret = runner.run_restore() + + # assertions + self.assertEqual(ret, length) + + if __name__ == '__main__': unittest.main() diff --git a/releasenotes/notes/mariadb-backup-b93c334f06498ff4.yaml b/releasenotes/notes/mariadb-backup-b93c334f06498ff4.yaml new file mode 100644 index 0000000000..95374ab6e2 --- /dev/null +++ b/releasenotes/notes/mariadb-backup-b93c334f06498ff4.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + For MariaDB datastore of versions equal or higher then 10.4, + `mariadb-backup` binary will be executed instead of `mariabackup` + when performing backup or restore process.