From ae6148c967782c70ed1756545737c1490f088200 Mon Sep 17 00:00:00 2001 From: Bharat Kumar Kobagana Date: Tue, 28 Jul 2015 17:01:36 +0530 Subject: [PATCH] GlusterFS backup driver This patch introduces GlusterFS storage as a backup target. GlusterFS backup driver inherits from Posix backup driver[1]. [1] https://review.openstack.org/#/c/163647/ Implements: blueprint glusterfs-as-a-backup-driver Change-Id: I4f0c36cdaa7cc417cb26bc1e908c6d107a71da35 --- cinder/backup/drivers/glusterfs.py | 94 ++++++++++++++++++ .../backup/drivers/test_backup_glusterfs.py | 99 +++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 cinder/backup/drivers/glusterfs.py create mode 100644 cinder/tests/unit/backup/drivers/test_backup_glusterfs.py diff --git a/cinder/backup/drivers/glusterfs.py b/cinder/backup/drivers/glusterfs.py new file mode 100644 index 00000000000..4da838fe6c2 --- /dev/null +++ b/cinder/backup/drivers/glusterfs.py @@ -0,0 +1,94 @@ +# Copyright (c) 2015 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Implementation of a backup service that uses GlusterFS as the backend.""" + +import os +import stat + +from os_brick.remotefs import remotefs as remotefs_brick +from oslo_concurrency import processutils as putils +from oslo_config import cfg + +from cinder.backup.drivers import posix +from cinder import exception +from cinder import utils + + +glusterfsbackup_service_opts = [ + cfg.StrOpt('glusterfs_backup_mount_point', + default='$state_path/backup_mount', + help='Base dir containing mount point for gluster share.'), + cfg.StrOpt('glusterfs_backup_share', + default=None, + help='GlusterFS share in ' + ': format. ' + 'Eg: 1.2.3.4:backup_vol'), +] + +CONF = cfg.CONF +CONF.register_opts(glusterfsbackup_service_opts) + + +class GlusterfsBackupDriver(posix.PosixBackupDriver): + """Provides backup, restore and delete using GlusterFS repository.""" + + def __init__(self, context, db_driver=None): + self._check_configuration() + self.backup_mount_point_base = CONF.glusterfs_backup_mount_point + self.backup_share = CONF.glusterfs_backup_share + self._execute = putils.execute + self._root_helper = utils.get_root_helper() + backup_path = self._init_backup_repo_path() + super(GlusterfsBackupDriver, self).__init__(context, + backup_path=backup_path) + + @staticmethod + def _check_configuration(): + """Raises error if any required configuration flag is missing.""" + required_flags = ['glusterfs_backup_share'] + for flag in required_flags: + if not getattr(CONF, flag, None): + raise exception.ConfigNotFound(path=flag) + + def _init_backup_repo_path(self): + remotefsclient = remotefs_brick.RemoteFsClient( + 'glusterfs', + self._root_helper, + glusterfs_mount_point_base=self.backup_mount_point_base) + remotefsclient.mount(self.backup_share) + + # Ensure we can write to this share + mount_path = remotefsclient.get_mount_point(self.backup_share) + + group_id = os.getegid() + current_group_id = utils.get_file_gid(mount_path) + current_mode = utils.get_file_mode(mount_path) + + if group_id != current_group_id: + cmd = ['chgrp', group_id, mount_path] + self._execute(*cmd, root_helper=self._root_helper, + run_as_root=True) + + if not (current_mode & stat.S_IWGRP): + cmd = ['chmod', 'g+w', mount_path] + self._execute(*cmd, root_helper=self._root_helper, + run_as_root=True) + + return mount_path + + +def get_backup_driver(context): + return GlusterfsBackupDriver(context) diff --git a/cinder/tests/unit/backup/drivers/test_backup_glusterfs.py b/cinder/tests/unit/backup/drivers/test_backup_glusterfs.py new file mode 100644 index 00000000000..a60b06f9d6f --- /dev/null +++ b/cinder/tests/unit/backup/drivers/test_backup_glusterfs.py @@ -0,0 +1,99 @@ +# Copyright (c) 2013 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Tests for GlusterFS backup driver. + +""" +import os + +import mock +from os_brick.remotefs import remotefs as remotefs_brick +from oslo_config import cfg + +from cinder.backup.drivers import glusterfs +from cinder import context +from cinder import exception +from cinder import test +from cinder import utils + +CONF = cfg.CONF + +FAKE_BACKUP_MOUNT_POINT_BASE = '/fake/mount-point-base' +FAKE_HOST = 'fake_host' +FAKE_VOL_NAME = 'backup_vol' +FAKE_BACKUP_SHARE = '%s:%s' % (FAKE_HOST, FAKE_VOL_NAME) +FAKE_BACKUP_PATH = os.path.join(FAKE_BACKUP_MOUNT_POINT_BASE, + 'e51e43e3c63fd5770e90e58e2eafc709') + + +class BackupGlusterfsShareTestCase(test.TestCase): + + def setUp(self): + super(BackupGlusterfsShareTestCase, self).setUp() + self.ctxt = context.get_admin_context() + + def test_check_configuration(self): + self.override_config('glusterfs_backup_share', FAKE_BACKUP_SHARE) + self.mock_object(glusterfs.GlusterfsBackupDriver, + '_init_backup_repo_path', + mock.Mock(return_value=FAKE_BACKUP_PATH)) + + with mock.patch.object(glusterfs.GlusterfsBackupDriver, + '_check_configuration'): + driver = glusterfs.GlusterfsBackupDriver(self.ctxt) + driver._check_configuration() + + def test_check_configuration_no_backup_share(self): + self.override_config('glusterfs_backup_share', None) + self.mock_object(glusterfs.GlusterfsBackupDriver, + '_init_backup_repo_path', + mock.Mock(return_value=FAKE_BACKUP_PATH)) + + with mock.patch.object(glusterfs.GlusterfsBackupDriver, + '_check_configuration'): + driver = glusterfs.GlusterfsBackupDriver(self.ctxt) + self.assertRaises(exception.ConfigNotFound, + driver._check_configuration) + + def test_init_backup_repo_path(self): + self.override_config('glusterfs_backup_share', FAKE_BACKUP_SHARE) + self.override_config('glusterfs_backup_mount_point', + FAKE_BACKUP_MOUNT_POINT_BASE) + mock_remotefsclient = mock.Mock() + mock_remotefsclient.get_mount_point = mock.Mock( + return_value=FAKE_BACKUP_PATH) + self.mock_object(glusterfs.GlusterfsBackupDriver, + '_check_configuration') + self.mock_object(remotefs_brick, 'RemoteFsClient', + mock.Mock(return_value=mock_remotefsclient)) + self.mock_object(os, 'getegid', + mock.Mock(return_value=333333)) + self.mock_object(utils, 'get_file_gid', + mock.Mock(return_value=333333)) + self.mock_object(utils, 'get_file_mode', + mock.Mock(return_value=00000)) + self.mock_object(utils, 'get_root_helper') + + with mock.patch.object(glusterfs.GlusterfsBackupDriver, + '_init_backup_repo_path'): + driver = glusterfs.GlusterfsBackupDriver(self.ctxt) + self.mock_object(driver, '_execute') + path = driver._init_backup_repo_path() + + self.assertEqual(FAKE_BACKUP_PATH, path) + utils.get_root_helper.called_once() + mock_remotefsclient.mount.assert_called_once_with(FAKE_BACKUP_SHARE) + mock_remotefsclient.get_mount_point.assert_called_once_with( + FAKE_BACKUP_SHARE)