import errno
import json
import os
+import tempfile
import mox as mox_lib
from mox import IgnoreArg
from cinder import test
from cinder.tests.compute import test_nova
from cinder import units
+from cinder import utils
from cinder.volume import configuration as conf
from cinder.volume.drivers import glusterfs
mox = self._mox
drv = self._driver
+ mox.StubOutWithMock(utils, 'get_file_mode')
+ mox.StubOutWithMock(utils, 'get_file_gid')
+ mox.StubOutWithMock(drv, '_execute')
+ mox.StubOutWithMock(drv, '_ensure_share_writable')
+
mox.StubOutWithMock(drv, '_get_mount_point_for_share')
drv._get_mount_point_for_share(self.TEST_EXPORT1).\
AndReturn(self.TEST_MNT_POINT)
drv._mount_glusterfs(self.TEST_EXPORT1, self.TEST_MNT_POINT,
ensure=True)
+ utils.get_file_gid(self.TEST_MNT_POINT).AndReturn(333333)
+
+ utils.get_file_mode(self.TEST_MNT_POINT).AndReturn(0o777)
+
+ drv._ensure_share_writable(self.TEST_MNT_POINT)
+
+ drv._execute('chgrp', IgnoreArg(), self.TEST_MNT_POINT,
+ run_as_root=True)
+
mox.ReplayAll()
drv._ensure_share_mounted(self.TEST_EXPORT1)
mox.VerifyAll()
+ def _fake_load_shares_config(self, conf):
+ self._driver.shares = {'127.7.7.7:/gluster1': None}
+
+ def _fake_NamedTemporaryFile(self, prefix=None, dir=None):
+ raise OSError('Permission denied!')
+
+ def test_setup_set_share_permissions(self):
+ mox = self._mox
+ drv = self._driver
+
+ glusterfs.CONF.glusterfs_shares_config = self.TEST_SHARES_CONFIG_FILE
+
+ self.stubs.Set(drv, '_load_shares_config',
+ self._fake_load_shares_config)
+ self.stubs.Set(tempfile, 'NamedTemporaryFile',
+ self._fake_NamedTemporaryFile)
+ mox.StubOutWithMock(os.path, 'exists')
+ mox.StubOutWithMock(drv, '_execute')
+ mox.StubOutWithMock(utils, 'get_file_gid')
+ mox.StubOutWithMock(utils, 'get_file_mode')
+ mox.StubOutWithMock(os, 'getegid')
+
+ drv._execute('mount.glusterfs', check_exit_code=False)
+
+ drv._execute('mkdir', '-p', mox_lib.IgnoreArg())
+
+ os.path.exists(self.TEST_SHARES_CONFIG_FILE).AndReturn(True)
+
+ drv._execute('mount', '-t', 'glusterfs', '127.7.7.7:/gluster1',
+ mox_lib.IgnoreArg(), run_as_root=True)
+
+ utils.get_file_gid(mox_lib.IgnoreArg()).AndReturn(33333)
+ # perms not writable
+ utils.get_file_mode(mox_lib.IgnoreArg()).AndReturn(0o000)
+
+ os.getegid().AndReturn(888)
+
+ drv._execute('chgrp', 888, mox_lib.IgnoreArg(), run_as_root=True)
+ drv._execute('chmod', 'g+w', mox_lib.IgnoreArg(), run_as_root=True)
+
+ mox.ReplayAll()
+
+ drv.do_setup(IsA(context.RequestContext))
+
+ mox.VerifyAll()
+
def test_find_share_should_throw_error_if_there_is_no_mounted_shares(self):
"""_find_share should throw error if there is no mounted shares."""
drv = self._driver
(mox, drv) = self._mox, self._driver
hashed = drv._get_hash_str(self.TEST_EXPORT1)
+ volume_dir = os.path.join(self.TEST_MNT_POINT_BASE, hashed)
volume_path = '%s/%s/volume-%s' % (self.TEST_MNT_POINT_BASE,
hashed,
self.VOLUME_UUID)
mox.StubOutWithMock(drv, '_get_backing_chain_for_path')
mox.StubOutWithMock(drv, '_get_matching_backing_file')
mox.StubOutWithMock(drv, '_write_info_file')
+ mox.StubOutWithMock(drv, '_ensure_share_writable')
mox.StubOutWithMock(image_utils, 'qemu_img_info')
+ drv._ensure_share_writable(volume_dir)
+
img_info = imageutils.QemuImgInfo(qemu_img_info_output)
image_utils.qemu_img_info(snap_path_2).AndReturn(img_info)
hashed = drv._get_hash_str(self.TEST_EXPORT1)
volume_file = 'volume-%s' % self.VOLUME_UUID
+ volume_dir = os.path.join(self.TEST_MNT_POINT_BASE, hashed)
volume_path = '%s/%s/%s' % (self.TEST_MNT_POINT_BASE,
hashed,
volume_file)
mox.StubOutWithMock(drv, '_write_info_file')
mox.StubOutWithMock(drv, '_get_backing_chain_for_path')
mox.StubOutWithMock(drv, 'get_active_image_from_info')
+ mox.StubOutWithMock(drv, '_ensure_share_writable')
mox.StubOutWithMock(image_utils, 'qemu_img_info')
info_file_dict = {self.SNAP_UUID_2: 'volume-%s.%s' %
self.SNAP_UUID: 'volume-%s.%s' %
(self.VOLUME_UUID, self.SNAP_UUID)}
+ drv._ensure_share_writable(volume_dir)
+
info_path = drv._local_path_volume(volume) + '.info'
drv._read_info_file(info_path).AndReturn(info_file_dict)
hashed = drv._get_hash_str(self.TEST_EXPORT1)
volume_file = 'volume-%s' % self.VOLUME_UUID
+ volume_dir = os.path.join(self.TEST_MNT_POINT_BASE, hashed)
volume_path = '%s/%s/%s' % (self.TEST_MNT_POINT_BASE,
hashed,
volume_file)
mox.StubOutWithMock(os.path, 'exists')
mox.StubOutWithMock(db, 'snapshot_get')
mox.StubOutWithMock(image_utils, 'qemu_img_info')
+ mox.StubOutWithMock(drv, '_ensure_share_writable')
snap_info = {'active': snap_file,
self.SNAP_UUID: snap_file}
+ drv._ensure_share_writable(volume_dir)
+
drv._read_info_file(info_path).AndReturn(snap_info)
os.path.exists(snap_path).AndReturn(True)
hashed = drv._get_hash_str(self.TEST_EXPORT1)
volume_file = 'volume-%s' % self.VOLUME_UUID
+ volume_dir = os.path.join(self.TEST_MNT_POINT_BASE, hashed)
volume_path = '%s/%s/%s' % (self.TEST_MNT_POINT_BASE,
hashed,
volume_file)
mox.StubOutWithMock(os.path, 'exists')
mox.StubOutWithMock(db, 'snapshot_get')
mox.StubOutWithMock(image_utils, 'qemu_img_info')
+ mox.StubOutWithMock(drv, '_ensure_share_writable')
snap_info = {'active': snap_file_2,
self.SNAP_UUID: snap_file,
self.SNAP_UUID_2: snap_file_2}
+ drv._ensure_share_writable(volume_dir)
+
drv._read_info_file(info_path).AndReturn(snap_info)
os.path.exists(snap_path).AndReturn(True)
import json
import os
import re
+import stat
+import tempfile
import time
from oslo.config import cfg
else:
raise
+ self._ensure_shares_mounted()
+
def check_for_setup_error(self):
"""Just to override parent behavior."""
pass
msg = _('Volume status must be "available" or "in-use".')
raise exception.InvalidVolume(msg)
+ self._ensure_share_writable(
+ self._local_volume_dir(snapshot['volume']))
+
# Determine the true snapshot file for this snapshot
# based on the .info file
info_path = self._local_path_volume(snapshot['volume']) + '.info'
LOG.debug(_('Available shares: %s') % str(self._mounted_shares))
+ def _ensure_share_writable(self, path):
+ """Ensure that the Cinder user can write to the share.
+
+ If not, raise an exception.
+
+ :param path: path to test
+ :raises: GlusterfsException
+ :returns: None
+ """
+
+ prefix = '.cinder-write-test-' + str(os.getpid()) + '-'
+
+ try:
+ tempfile.NamedTemporaryFile(prefix=prefix, dir=path)
+ except OSError:
+ msg = _('GlusterFS share at %(dir)s is not writable by the '
+ 'Cinder volume service. Snapshot operations will not be '
+ 'supported.') % {'dir': path}
+ raise exception.GlusterfsException(msg)
+
def _ensure_share_mounted(self, glusterfs_share):
"""Mount GlusterFS share.
:param glusterfs_share: string
mount_path = self._get_mount_point_for_share(glusterfs_share)
self._mount_glusterfs(glusterfs_share, mount_path, ensure=True)
+ # Ensure we can write to this 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, run_as_root=True)
+
+ if not (current_mode & stat.S_IWGRP):
+ cmd = ['chmod', 'g+w', mount_path]
+ self._execute(*cmd, run_as_root=True)
+
+ self._ensure_share_writable(mount_path)
+
def _find_share(self, volume_size_for):
"""Choose GlusterFS share among available ones for given volume size.
Current implementation looks for greatest capacity.