Merge "Adds a Cache for Volumes Created from Snapshots with Quobyte"
This commit is contained in:
commit
53467b9a60
@ -18,6 +18,7 @@
|
||||
import errno
|
||||
import os
|
||||
import psutil
|
||||
import shutil
|
||||
import six
|
||||
import traceback
|
||||
|
||||
@ -62,6 +63,18 @@ class QuobyteDriverTestCase(test.TestCase):
|
||||
SNAP_UUID = 'bacadaca-baca-daca-baca-dacadacadaca'
|
||||
SNAP_UUID_2 = 'bebedede-bebe-dede-bebe-dedebebedede'
|
||||
|
||||
def _get_fake_snapshot(self, src_volume):
|
||||
snapshot = fake_snapshot.fake_snapshot_obj(
|
||||
self.context,
|
||||
volume_name=src_volume.name,
|
||||
display_name='clone-snap-%s' % src_volume.id,
|
||||
size=src_volume.size,
|
||||
volume_size=src_volume.size,
|
||||
volume_id=src_volume.id,
|
||||
id=self.SNAP_UUID)
|
||||
snapshot.volume = src_volume
|
||||
return snapshot
|
||||
|
||||
def setUp(self):
|
||||
super(QuobyteDriverTestCase, self).setUp()
|
||||
|
||||
@ -76,6 +89,7 @@ class QuobyteDriverTestCase(test.TestCase):
|
||||
self.TEST_MNT_POINT_BASE
|
||||
self._configuration.nas_secure_file_operations = "auto"
|
||||
self._configuration.nas_secure_file_permissions = "auto"
|
||||
self._configuration.quobyte_volume_from_snapshot_cache = False
|
||||
|
||||
self._driver =\
|
||||
quobyte.QuobyteDriver(configuration=self._configuration,
|
||||
@ -118,6 +132,62 @@ class QuobyteDriverTestCase(test.TestCase):
|
||||
'fallocate', '-l', '%sG' % test_size, tmp_path,
|
||||
run_as_root=self._driver._execute_as_root)
|
||||
|
||||
@mock.patch.object(os, "makedirs")
|
||||
@mock.patch.object(os.path, "join", return_value="dummy_path")
|
||||
@mock.patch.object(os, "access", return_value=True)
|
||||
def test__ensure_volume_cache_ok(self, os_access_mock, os_join_mock,
|
||||
os_makedirs_mock):
|
||||
tmp_path = "/some/random/path"
|
||||
|
||||
self._driver._ensure_volume_from_snap_cache(tmp_path)
|
||||
|
||||
calls = [mock.call("dummy_path", os.F_OK),
|
||||
mock.call("dummy_path", os.R_OK),
|
||||
mock.call("dummy_path", os.W_OK),
|
||||
mock.call("dummy_path", os.X_OK)]
|
||||
os_access_mock.assert_has_calls(calls)
|
||||
os_join_mock.assert_called_once_with(
|
||||
tmp_path, self._driver.QUOBYTE_VOLUME_SNAP_CACHE_DIR_NAME)
|
||||
self.assertFalse(os_makedirs_mock.called)
|
||||
|
||||
@mock.patch.object(os, "makedirs")
|
||||
@mock.patch.object(os.path, "join", return_value="dummy_path")
|
||||
@mock.patch.object(os, "access", return_value=True)
|
||||
def test__ensure_volume_cache_create(self, os_access_mock, os_join_mock,
|
||||
os_makedirs_mock):
|
||||
tmp_path = "/some/random/path"
|
||||
os_access_mock.side_effect = [False, True, True, True]
|
||||
|
||||
self._driver._ensure_volume_from_snap_cache(tmp_path)
|
||||
|
||||
calls = [mock.call("dummy_path", os.F_OK),
|
||||
mock.call("dummy_path", os.R_OK),
|
||||
mock.call("dummy_path", os.W_OK),
|
||||
mock.call("dummy_path", os.X_OK)]
|
||||
os_access_mock.assert_has_calls(calls)
|
||||
os_join_mock.assert_called_once_with(
|
||||
tmp_path, self._driver.QUOBYTE_VOLUME_SNAP_CACHE_DIR_NAME)
|
||||
os_makedirs_mock.assert_called_once_with("dummy_path")
|
||||
|
||||
@mock.patch.object(os, "makedirs")
|
||||
@mock.patch.object(os.path, "join", return_value="dummy_path")
|
||||
@mock.patch.object(os, "access", return_value=True)
|
||||
def test__ensure_volume_cache_error(self, os_access_mock, os_join_mock,
|
||||
os_makedirs_mock):
|
||||
tmp_path = "/some/random/path"
|
||||
os_access_mock.side_effect = [True, False, False, False]
|
||||
|
||||
self.assertRaises(
|
||||
exception.VolumeDriverException,
|
||||
self._driver._ensure_volume_from_snap_cache, tmp_path)
|
||||
|
||||
calls = [mock.call("dummy_path", os.F_OK),
|
||||
mock.call("dummy_path", os.R_OK)]
|
||||
os_access_mock.assert_has_calls(calls)
|
||||
os_join_mock.assert_called_once_with(
|
||||
tmp_path, self._driver.QUOBYTE_VOLUME_SNAP_CACHE_DIR_NAME)
|
||||
self.assertFalse(os_makedirs_mock.called)
|
||||
|
||||
def test_local_path(self):
|
||||
"""local_path common use case."""
|
||||
drv = self._driver
|
||||
@ -166,8 +236,8 @@ class QuobyteDriverTestCase(test.TestCase):
|
||||
self.TEST_MNT_POINT)
|
||||
mock_validate.assert_called_once_with(self.TEST_MNT_POINT)
|
||||
|
||||
def test_mount_quobyte_should_suppress_and_log_already_mounted_error(self):
|
||||
"""test_mount_quobyte_should_suppress_and_log_already_mounted_error
|
||||
def test_mount_quobyte_should_suppress_already_mounted_error(self):
|
||||
"""test_mount_quobyte_should_suppress_already_mounted_error
|
||||
|
||||
Based on /proc/mount, the file system is not mounted yet. However,
|
||||
mount.quobyte returns with an 'already mounted' error. This is
|
||||
@ -175,12 +245,13 @@ class QuobyteDriverTestCase(test.TestCase):
|
||||
successful.
|
||||
|
||||
Because _mount_quobyte gets called with ensure=True, the error will
|
||||
be suppressed and logged instead.
|
||||
be suppressed instead.
|
||||
"""
|
||||
with mock.patch.object(self._driver, '_execute') as mock_execute, \
|
||||
mock.patch('cinder.volume.drivers.quobyte.QuobyteDriver'
|
||||
'.read_proc_mount') as mock_open, \
|
||||
mock.patch('cinder.volume.drivers.quobyte.LOG') as mock_LOG:
|
||||
mock.patch('cinder.volume.drivers.quobyte.QuobyteDriver'
|
||||
'._validate_volume') as mock_validate:
|
||||
# Content of /proc/mount (empty).
|
||||
mock_open.return_value = six.StringIO()
|
||||
mock_execute.side_effect = [None, putils.ProcessExecutionError(
|
||||
@ -196,14 +267,12 @@ class QuobyteDriverTestCase(test.TestCase):
|
||||
self.TEST_MNT_POINT, run_as_root=False)
|
||||
mock_execute.assert_has_calls([mkdir_call, mount_call],
|
||||
any_order=False)
|
||||
|
||||
mock_LOG.warning.assert_called_once_with('%s is already mounted',
|
||||
self.TEST_QUOBYTE_VOLUME)
|
||||
mock_validate.assert_called_once_with(self.TEST_MNT_POINT)
|
||||
|
||||
def test_mount_quobyte_should_reraise_already_mounted_error(self):
|
||||
"""test_mount_quobyte_should_reraise_already_mounted_error
|
||||
|
||||
Like test_mount_quobyte_should_suppress_and_log_already_mounted_error
|
||||
Like test_mount_quobyte_should_suppress_already_mounted_error
|
||||
but with ensure=False.
|
||||
"""
|
||||
with mock.patch.object(self._driver, '_execute') as mock_execute, \
|
||||
@ -228,6 +297,68 @@ class QuobyteDriverTestCase(test.TestCase):
|
||||
mock_execute.assert_has_calls([mkdir_call, mount_call],
|
||||
any_order=False)
|
||||
|
||||
@mock.patch.object(image_utils, "qemu_img_info")
|
||||
def test_optimize_volume_not(self, iu_qii_mock):
|
||||
drv = self._driver
|
||||
vol = self._simple_volume()
|
||||
vol.size = 3
|
||||
img_data = mock.Mock()
|
||||
img_data.disk_size = 3 * units.Gi
|
||||
iu_qii_mock.return_value = img_data
|
||||
drv._execute = mock.Mock()
|
||||
drv._create_regular_file = mock.Mock()
|
||||
drv.local_path = mock.Mock(return_value="/some/path")
|
||||
|
||||
drv.optimize_volume(vol)
|
||||
|
||||
iu_qii_mock.assert_called_once_with("/some/path",
|
||||
run_as_root=drv._execute_as_root)
|
||||
self.assertFalse(drv._execute.called)
|
||||
self.assertFalse(drv._create_regular_file.called)
|
||||
|
||||
@mock.patch.object(image_utils, "qemu_img_info")
|
||||
def test_optimize_volume_sparse(self, iu_qii_mock):
|
||||
drv = self._driver
|
||||
vol = self._simple_volume()
|
||||
vol.size = 3
|
||||
img_data = mock.Mock()
|
||||
img_data.disk_size = 2 * units.Gi
|
||||
iu_qii_mock.return_value = img_data
|
||||
drv._execute = mock.Mock()
|
||||
drv._create_regular_file = mock.Mock()
|
||||
drv.local_path = mock.Mock(return_value="/some/path")
|
||||
|
||||
drv.optimize_volume(vol)
|
||||
|
||||
iu_qii_mock.assert_called_once_with(drv.local_path(),
|
||||
run_as_root=drv._execute_as_root)
|
||||
drv._execute.assert_called_once_with(
|
||||
'truncate', '-s', '%sG' % vol.size, drv.local_path(),
|
||||
run_as_root=drv._execute_as_root)
|
||||
self.assertFalse(drv._create_regular_file.called)
|
||||
|
||||
@mock.patch.object(image_utils, "qemu_img_info")
|
||||
def test_optimize_volume_regular(self, iu_qii_mock):
|
||||
drv = self._driver
|
||||
drv.configuration.quobyte_qcow2_volumes = False
|
||||
drv.configuration.quobyte_sparsed_volumes = False
|
||||
vol = self._simple_volume()
|
||||
vol.size = 3
|
||||
img_data = mock.Mock()
|
||||
img_data.disk_size = 2 * units.Gi
|
||||
iu_qii_mock.return_value = img_data
|
||||
drv._execute = mock.Mock()
|
||||
drv._create_regular_file = mock.Mock()
|
||||
drv.local_path = mock.Mock(return_value="/some/path")
|
||||
|
||||
drv.optimize_volume(vol)
|
||||
|
||||
iu_qii_mock.assert_called_once_with(drv.local_path(),
|
||||
run_as_root=drv._execute_as_root)
|
||||
self.assertFalse(drv._execute.called)
|
||||
drv._create_regular_file.assert_called_once_with(drv.local_path(),
|
||||
vol.size)
|
||||
|
||||
def test_get_hash_str(self):
|
||||
"""_get_hash_str should calculation correct value."""
|
||||
drv = self._driver
|
||||
@ -643,15 +774,7 @@ class QuobyteDriverTestCase(test.TestCase):
|
||||
dest_vol_path = os.path.join(vol_dir, dest_volume['name'])
|
||||
info_path = os.path.join(vol_dir, src_volume['name']) + '.info'
|
||||
|
||||
snapshot = fake_snapshot.fake_snapshot_obj(
|
||||
self.context,
|
||||
volume_name=src_volume.name,
|
||||
display_name='clone-snap-%s' % src_volume.id,
|
||||
size=src_volume.size,
|
||||
volume_size=src_volume.size,
|
||||
volume_id=src_volume.id,
|
||||
id=self.SNAP_UUID)
|
||||
snapshot.volume = src_volume
|
||||
snapshot = self._get_fake_snapshot(src_volume)
|
||||
|
||||
snap_file = dest_volume['name'] + '.' + snapshot['id']
|
||||
snap_path = os.path.join(vol_dir, snap_file)
|
||||
@ -672,9 +795,8 @@ class QuobyteDriverTestCase(test.TestCase):
|
||||
{'active': snap_file,
|
||||
snapshot['id']: snap_file})
|
||||
image_utils.qemu_img_info = mock.Mock(return_value=img_info)
|
||||
drv._set_rw_permissions_for_all = mock.Mock()
|
||||
drv._find_share = mock.Mock()
|
||||
drv._find_share.return_value = "/some/arbitrary/path"
|
||||
drv._set_rw_permissions = mock.Mock()
|
||||
drv.optimize_volume = mock.Mock()
|
||||
|
||||
drv._copy_volume_from_snapshot(snapshot, dest_volume, size)
|
||||
|
||||
@ -687,7 +809,124 @@ class QuobyteDriverTestCase(test.TestCase):
|
||||
dest_vol_path,
|
||||
'raw',
|
||||
run_as_root=self._driver._execute_as_root))
|
||||
drv._set_rw_permissions_for_all.assert_called_once_with(dest_vol_path)
|
||||
drv._set_rw_permissions.assert_called_once_with(dest_vol_path)
|
||||
drv.optimize_volume.assert_called_once_with(dest_volume)
|
||||
|
||||
@mock.patch.object(os, "access", return_value=True)
|
||||
def test_copy_volume_from_snapshot_cached(self, os_ac_mock):
|
||||
drv = self._driver
|
||||
drv.configuration.quobyte_volume_from_snapshot_cache = True
|
||||
|
||||
# lots of test vars to be prepared at first
|
||||
dest_volume = self._simple_volume(
|
||||
id='c1073000-0000-0000-0000-0000000c1073')
|
||||
src_volume = self._simple_volume()
|
||||
|
||||
vol_dir = os.path.join(self.TEST_MNT_POINT_BASE,
|
||||
drv._get_hash_str(self.TEST_QUOBYTE_VOLUME))
|
||||
dest_vol_path = os.path.join(vol_dir, dest_volume['name'])
|
||||
info_path = os.path.join(vol_dir, src_volume['name']) + '.info'
|
||||
|
||||
snapshot = self._get_fake_snapshot(src_volume)
|
||||
|
||||
snap_file = dest_volume['name'] + '.' + snapshot['id']
|
||||
snap_path = os.path.join(vol_dir, snap_file)
|
||||
cache_path = os.path.join(vol_dir,
|
||||
drv.QUOBYTE_VOLUME_SNAP_CACHE_DIR_NAME,
|
||||
snapshot['id'])
|
||||
|
||||
size = dest_volume['size']
|
||||
|
||||
qemu_img_output = """image: %s
|
||||
file format: raw
|
||||
virtual size: 1.0G (1073741824 bytes)
|
||||
disk size: 173K
|
||||
backing file: %s
|
||||
""" % (snap_file, src_volume['name'])
|
||||
img_info = imageutils.QemuImgInfo(qemu_img_output)
|
||||
|
||||
# mocking and testing starts here
|
||||
image_utils.convert_image = mock.Mock()
|
||||
drv._read_info_file = mock.Mock(return_value=
|
||||
{'active': snap_file,
|
||||
snapshot['id']: snap_file})
|
||||
image_utils.qemu_img_info = mock.Mock(return_value=img_info)
|
||||
drv._set_rw_permissions = mock.Mock()
|
||||
shutil.copyfile = mock.Mock()
|
||||
drv.optimize_volume = mock.Mock()
|
||||
|
||||
drv._copy_volume_from_snapshot(snapshot, dest_volume, size)
|
||||
|
||||
drv._read_info_file.assert_called_once_with(info_path)
|
||||
image_utils.qemu_img_info.assert_called_once_with(snap_path,
|
||||
force_share=False,
|
||||
run_as_root=False)
|
||||
self.assertFalse(image_utils.convert_image.called,
|
||||
("_convert_image was called but should not have been")
|
||||
)
|
||||
os_ac_mock.assert_called_once_with(
|
||||
drv._local_volume_from_snap_cache_path(snapshot), os.F_OK)
|
||||
shutil.copyfile.assert_called_once_with(cache_path, dest_vol_path)
|
||||
drv._set_rw_permissions.assert_called_once_with(dest_vol_path)
|
||||
drv.optimize_volume.assert_called_once_with(dest_volume)
|
||||
|
||||
def test_copy_volume_from_snapshot_not_cached(self):
|
||||
drv = self._driver
|
||||
drv.configuration.quobyte_volume_from_snapshot_cache = True
|
||||
|
||||
# lots of test vars to be prepared at first
|
||||
dest_volume = self._simple_volume(
|
||||
id='c1073000-0000-0000-0000-0000000c1073')
|
||||
src_volume = self._simple_volume()
|
||||
|
||||
vol_dir = os.path.join(self.TEST_MNT_POINT_BASE,
|
||||
drv._get_hash_str(self.TEST_QUOBYTE_VOLUME))
|
||||
src_vol_path = os.path.join(vol_dir, src_volume['name'])
|
||||
dest_vol_path = os.path.join(vol_dir, dest_volume['name'])
|
||||
info_path = os.path.join(vol_dir, src_volume['name']) + '.info'
|
||||
|
||||
snapshot = self._get_fake_snapshot(src_volume)
|
||||
|
||||
snap_file = dest_volume['name'] + '.' + snapshot['id']
|
||||
snap_path = os.path.join(vol_dir, snap_file)
|
||||
cache_path = os.path.join(vol_dir,
|
||||
drv.QUOBYTE_VOLUME_SNAP_CACHE_DIR_NAME,
|
||||
snapshot['id'])
|
||||
|
||||
size = dest_volume['size']
|
||||
|
||||
qemu_img_output = """image: %s
|
||||
file format: raw
|
||||
virtual size: 1.0G (1073741824 bytes)
|
||||
disk size: 173K
|
||||
backing file: %s
|
||||
""" % (snap_file, src_volume['name'])
|
||||
img_info = imageutils.QemuImgInfo(qemu_img_output)
|
||||
|
||||
# mocking and testing starts here
|
||||
image_utils.convert_image = mock.Mock()
|
||||
drv._read_info_file = mock.Mock(return_value=
|
||||
{'active': snap_file,
|
||||
snapshot['id']: snap_file})
|
||||
image_utils.qemu_img_info = mock.Mock(return_value=img_info)
|
||||
drv._set_rw_permissions = mock.Mock()
|
||||
shutil.copyfile = mock.Mock()
|
||||
drv.optimize_volume = mock.Mock()
|
||||
|
||||
drv._copy_volume_from_snapshot(snapshot, dest_volume, size)
|
||||
|
||||
drv._read_info_file.assert_called_once_with(info_path)
|
||||
image_utils.qemu_img_info.assert_called_once_with(snap_path,
|
||||
force_share=False,
|
||||
run_as_root=False)
|
||||
(image_utils.convert_image.
|
||||
assert_called_once_with(
|
||||
src_vol_path,
|
||||
drv._local_volume_from_snap_cache_path(snapshot), 'raw',
|
||||
run_as_root=self._driver._execute_as_root))
|
||||
shutil.copyfile.assert_called_once_with(cache_path, dest_vol_path)
|
||||
drv._set_rw_permissions.assert_called_once_with(dest_vol_path)
|
||||
drv.optimize_volume.assert_called_once_with(dest_volume)
|
||||
|
||||
def test_create_volume_from_snapshot_status_not_available(self):
|
||||
"""Expect an error when the snapshot's status is not 'available'."""
|
||||
|
@ -17,13 +17,16 @@
|
||||
import errno
|
||||
import os
|
||||
import psutil
|
||||
import shutil
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import fileutils
|
||||
from oslo_utils import units
|
||||
|
||||
from cinder import compute
|
||||
from cinder import coordination
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder.image import image_utils
|
||||
@ -32,7 +35,7 @@ from cinder import utils
|
||||
from cinder.volume import configuration
|
||||
from cinder.volume.drivers import remotefs as remotefs_drv
|
||||
|
||||
VERSION = '1.1.7'
|
||||
VERSION = '1.1.8'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -54,6 +57,11 @@ volume_opts = [
|
||||
default='$state_path/mnt',
|
||||
help=('Base dir containing the mount point'
|
||||
' for the Quobyte volume.')),
|
||||
cfg.BoolOpt('quobyte_volume_from_snapshot_cache',
|
||||
default=False,
|
||||
help=('Create a cache of volumes from merged snapshots to '
|
||||
'speed up creation of multiple volumes from a single '
|
||||
'snapshot.'))
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -88,6 +96,7 @@ class QuobyteDriver(remotefs_drv.RemoteFSSnapDriverDistributed):
|
||||
1.1.5 - Enables extension of volumes with snapshots
|
||||
1.1.6 - Optimizes volume creation
|
||||
1.1.7 - Support fuse subtype based Quobyte mount validation
|
||||
1.1.8 - Adds optional snapshot merge caching
|
||||
|
||||
"""
|
||||
|
||||
@ -99,6 +108,8 @@ class QuobyteDriver(remotefs_drv.RemoteFSSnapDriverDistributed):
|
||||
# ThirdPartySystems wiki page
|
||||
CI_WIKI_NAME = "Quobyte_CI"
|
||||
|
||||
QUOBYTE_VOLUME_SNAP_CACHE_DIR_NAME = "volume_from_snapshot_cache"
|
||||
|
||||
def __init__(self, execute=processutils.execute, *args, **kwargs):
|
||||
super(QuobyteDriver, self).__init__(*args, **kwargs)
|
||||
self.configuration.append_config_values(volume_opts)
|
||||
@ -111,6 +122,32 @@ class QuobyteDriver(remotefs_drv.RemoteFSSnapDriverDistributed):
|
||||
self._execute('fallocate', '-l', '%sG' % size,
|
||||
path, run_as_root=self._execute_as_root)
|
||||
|
||||
def _ensure_volume_from_snap_cache(self, mount_path):
|
||||
"""This expects the Quobyte volume to be mounted & available"""
|
||||
cache_path = os.path.join(mount_path,
|
||||
self.QUOBYTE_VOLUME_SNAP_CACHE_DIR_NAME)
|
||||
if not os.access(cache_path, os.F_OK):
|
||||
LOG.info("Volume from snapshot cache directory does not exist, "
|
||||
"creating the directory %(volcache)s",
|
||||
{'volcache': cache_path})
|
||||
os.makedirs(cache_path)
|
||||
if not (os.access(cache_path, os.R_OK)
|
||||
and os.access(cache_path, os.W_OK)
|
||||
and os.access(cache_path, os.X_OK)):
|
||||
msg = _("Insufficient permissions for Quobyte volume from "
|
||||
"snapshot cache directory at %(cpath)s. Please update "
|
||||
"permissions.") % {'cpath': cache_path}
|
||||
raise exception.VolumeDriverException(msg)
|
||||
LOG.debug("Quobyte volume from snapshot cache directory validated ok")
|
||||
|
||||
def _local_volume_from_snap_cache_path(self, snapshot):
|
||||
path_to_disk = os.path.join(
|
||||
self._local_volume_dir(snapshot.volume),
|
||||
self.QUOBYTE_VOLUME_SNAP_CACHE_DIR_NAME,
|
||||
snapshot.id)
|
||||
|
||||
return path_to_disk
|
||||
|
||||
def do_setup(self, context):
|
||||
"""Any initialization the volume driver does while starting."""
|
||||
super(QuobyteDriver, self).do_setup(context)
|
||||
@ -138,6 +175,32 @@ class QuobyteDriver(remotefs_drv.RemoteFSSnapDriverDistributed):
|
||||
else:
|
||||
raise
|
||||
|
||||
def optimize_volume(self, volume):
|
||||
"""Optimizes a volume for Quobyte
|
||||
|
||||
This optimization is normally done during creation but volumes created
|
||||
from e.g. snapshots require additional grooming.
|
||||
|
||||
:param volume: volume reference
|
||||
"""
|
||||
volume_path = self.local_path(volume)
|
||||
volume_size = volume.size
|
||||
data = image_utils.qemu_img_info(self.local_path(volume),
|
||||
run_as_root=self._execute_as_root)
|
||||
if data.disk_size >= (volume_size * units.Gi):
|
||||
LOG.debug("Optimization of volume %(volpath)s is not required, "
|
||||
"skipping this step.", {'volpath': volume_path})
|
||||
return
|
||||
|
||||
LOG.debug("Optimizing volume %(optpath)s", {'optpath': volume_path})
|
||||
|
||||
if (self.configuration.quobyte_qcow2_volumes or
|
||||
self.configuration.quobyte_sparsed_volumes):
|
||||
self._execute('truncate', '-s', '%sG' % volume_size,
|
||||
volume_path, run_as_root=self._execute_as_root)
|
||||
else:
|
||||
self._create_regular_file(volume_path, volume_size)
|
||||
|
||||
def set_nas_security_options(self, is_new_cinder_install):
|
||||
self._execute_as_root = False
|
||||
|
||||
@ -222,18 +285,20 @@ class QuobyteDriver(remotefs_drv.RemoteFSSnapDriverDistributed):
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
return self._create_volume_from_snapshot(volume, snapshot)
|
||||
|
||||
@coordination.synchronized('{self.driver_prefix}-{snapshot.volume.id}')
|
||||
def _copy_volume_from_snapshot(self, snapshot, volume, volume_size):
|
||||
"""Copy data from snapshot to destination volume.
|
||||
|
||||
This is done with a qemu-img convert to raw/qcow2 from the snapshot
|
||||
qcow2.
|
||||
qcow2. If the quobyte_volume_from_snapshot_cache is active the result
|
||||
is copied into the cache and all volumes created from this
|
||||
snapshot id are directly copied from the cache.
|
||||
"""
|
||||
|
||||
LOG.debug("snapshot: %(snap)s, volume: %(vol)s, ",
|
||||
{'snap': snapshot.id,
|
||||
'vol': volume.id,
|
||||
'size': volume_size})
|
||||
|
||||
info_path = self._local_path_volume_info(snapshot.volume)
|
||||
snap_info = self._read_info_file(info_path)
|
||||
vol_path = self._local_volume_dir(snapshot.volume)
|
||||
@ -248,6 +313,7 @@ class QuobyteDriver(remotefs_drv.RemoteFSSnapDriverDistributed):
|
||||
path_to_snap_img = os.path.join(vol_path, img_info.backing_file)
|
||||
|
||||
path_to_new_vol = self._local_path_volume(volume)
|
||||
path_to_cached_vol = self._local_volume_from_snap_cache_path(snapshot)
|
||||
|
||||
LOG.debug("will copy from snapshot at %s", path_to_snap_img)
|
||||
|
||||
@ -256,12 +322,27 @@ class QuobyteDriver(remotefs_drv.RemoteFSSnapDriverDistributed):
|
||||
else:
|
||||
out_format = 'raw'
|
||||
|
||||
image_utils.convert_image(path_to_snap_img,
|
||||
path_to_new_vol,
|
||||
out_format,
|
||||
run_as_root=self._execute_as_root)
|
||||
|
||||
self._set_rw_permissions_for_all(path_to_new_vol)
|
||||
if not self.configuration.quobyte_volume_from_snapshot_cache:
|
||||
LOG.debug("Creating direct copy from snapshot")
|
||||
image_utils.convert_image(path_to_snap_img,
|
||||
path_to_new_vol,
|
||||
out_format,
|
||||
run_as_root=self._execute_as_root)
|
||||
else:
|
||||
# create the volume via volume cache
|
||||
if not os.access(path_to_cached_vol, os.F_OK):
|
||||
LOG.debug("Caching volume %(volpath)s from snapshot.",
|
||||
{'volpath': path_to_cached_vol})
|
||||
image_utils.convert_image(path_to_snap_img,
|
||||
path_to_cached_vol,
|
||||
out_format,
|
||||
run_as_root=self._execute_as_root)
|
||||
# Copy volume from cache
|
||||
LOG.debug("Copying volume %(volpath)s from cache",
|
||||
{'volpath': path_to_new_vol})
|
||||
shutil.copyfile(path_to_cached_vol, path_to_new_vol)
|
||||
self._set_rw_permissions(path_to_new_vol)
|
||||
self.optimize_volume(volume)
|
||||
|
||||
@utils.synchronized('quobyte', external=False)
|
||||
def delete_volume(self, volume):
|
||||
@ -299,6 +380,9 @@ class QuobyteDriver(remotefs_drv.RemoteFSSnapDriverDistributed):
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Apply locking to the delete snapshot operation."""
|
||||
self._delete_snapshot(snapshot)
|
||||
if self.configuration.quobyte_volume_from_snapshot_cache:
|
||||
fileutils.delete_if_exists(
|
||||
self._local_volume_from_snap_cache_path(snapshot))
|
||||
|
||||
@utils.synchronized('quobyte', external=False)
|
||||
def initialize_connection(self, volume, connector):
|
||||
@ -495,11 +579,14 @@ class QuobyteDriver(remotefs_drv.RemoteFSSnapDriverDistributed):
|
||||
except processutils.ProcessExecutionError as exc:
|
||||
if ensure and 'already mounted' in exc.stderr:
|
||||
LOG.warning("%s is already mounted", quobyte_volume)
|
||||
mounted = True
|
||||
else:
|
||||
raise
|
||||
|
||||
if mounted:
|
||||
self._validate_volume(mount_path)
|
||||
if self.configuration.quobyte_volume_from_snapshot_cache:
|
||||
self._ensure_volume_from_snap_cache(mount_path)
|
||||
|
||||
def _validate_volume(self, mount_path):
|
||||
"""Runs a number of tests on the expect Quobyte mount"""
|
||||
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
|
||||
fixes:
|
||||
- |
|
||||
Added a new optional cache of volumes generated from snapshots for the
|
||||
Quobyte backend. Enabling this cache speeds up creation of multiple
|
||||
volumes from a single snapshot at the cost of a slight increase in
|
||||
creation time for the first volume generated for this given snapshot.
|
||||
The ``quobyte_volume_from_snapshot_cache`` option is off by default.
|
Loading…
Reference in New Issue
Block a user