Merge "GlusterFS backup driver"
This commit is contained in:
commit
c688a0af52
94
cinder/backup/drivers/glusterfs.py
Normal file
94
cinder/backup/drivers/glusterfs.py
Normal file
@ -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 '
|
||||
'<hostname|ipv4addr|ipv6addr>:<gluster_vol_name> 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)
|
99
cinder/tests/unit/backup/drivers/test_backup_glusterfs.py
Normal file
99
cinder/tests/unit/backup/drivers/test_backup_glusterfs.py
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user