+++ /dev/null
-# Copyright (c) 2014 Quobyte Inc.
-# 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.
-"""Unit tests for the Quobyte driver module."""
-
-import errno
-import os
-import six
-import traceback
-
-import mock
-from oslo_concurrency import processutils as putils
-from oslo_config import cfg
-from oslo_utils import units
-
-from cinder import context
-from cinder import exception
-from cinder.image import image_utils
-from cinder.openstack.common import imageutils
-from cinder import test
-from cinder.volume import configuration as conf
-from cinder.volume.drivers import quobyte
-
-
-CONF = cfg.CONF
-
-
-class DumbVolume(object):
- fields = {}
-
- def __setitem__(self, key, value):
- self.fields[key] = value
-
- def __getitem__(self, item):
- return self.fields[item]
-
-
-class FakeDb(object):
- msg = "Tests are broken: mock this out."
-
- def volume_get(self, *a, **kw):
- raise Exception(self.msg)
-
- def snapshot_get_all_for_volume(self, *a, **kw):
- """Mock this if you want results from it."""
- return []
-
-
-class QuobyteDriverTestCase(test.TestCase):
- """Test case for Quobyte driver."""
-
- TEST_QUOBYTE_VOLUME = 'quobyte://quobyte-host/openstack-volumes'
- TEST_QUOBYTE_VOLUME_WITHOUT_PROTOCOL = 'quobyte-host/openstack-volumes'
- TEST_SIZE_IN_GB = 1
- TEST_MNT_POINT = '/mnt/quobyte'
- TEST_MNT_POINT_BASE = '/mnt'
- TEST_LOCAL_PATH = '/mnt/quobyte/volume-123'
- TEST_FILE_NAME = 'test.txt'
- TEST_SHARES_CONFIG_FILE = '/etc/cinder/test-shares.conf'
- TEST_TMP_FILE = '/tmp/tempfile'
- VOLUME_UUID = 'abcdefab-cdef-abcd-efab-cdefabcdefab'
- SNAP_UUID = 'bacadaca-baca-daca-baca-dacadacadaca'
- SNAP_UUID_2 = 'bebedede-bebe-dede-bebe-dedebebedede'
-
- def setUp(self):
- super(QuobyteDriverTestCase, self).setUp()
-
- self._configuration = mock.Mock(conf.Configuration)
- self._configuration.append_config_values(mock.ANY)
- self._configuration.quobyte_volume_url = \
- self.TEST_QUOBYTE_VOLUME
- self._configuration.quobyte_client_cfg = None
- self._configuration.quobyte_sparsed_volumes = True
- self._configuration.quobyte_qcow2_volumes = False
- self._configuration.quobyte_mount_point_base = \
- self.TEST_MNT_POINT_BASE
-
- self._driver =\
- quobyte.QuobyteDriver(configuration=self._configuration,
- db=FakeDb())
- self._driver.shares = {}
- self._driver.set_nas_security_options(is_new_cinder_install=False)
-
- def assertRaisesAndMessageMatches(
- self, excClass, msg, callableObj, *args, **kwargs):
- """Ensure that the specified exception was raised and its message
- includes the string 'msg'.
- """
-
- caught = False
- try:
- callableObj(*args, **kwargs)
- except Exception as exc:
- caught = True
- self.assertEqual(excClass, type(exc),
- 'Wrong exception caught: %s Stacktrace: %s' %
- (exc, traceback.format_exc()))
- self.assertIn(msg, six.text_type(exc))
-
- if not caught:
- self.fail('Expected raised exception but nothing caught.')
-
- def test_local_path(self):
- """local_path common use case."""
- drv = self._driver
-
- volume = DumbVolume()
- volume['provider_location'] = self.TEST_QUOBYTE_VOLUME
- volume['name'] = 'volume-123'
-
- self.assertEqual(
- '/mnt/1331538734b757ed52d0e18c0a7210cd/volume-123',
- drv.local_path(volume))
-
- def test_mount_quobyte_should_mount_correctly(self):
- with mock.patch.object(self._driver, '_execute') as mock_execute, \
- mock.patch('cinder.volume.drivers.quobyte.QuobyteDriver'
- '.read_proc_mount') as mock_open:
- # Content of /proc/mount (not mounted yet).
- mock_open.return_value = six.StringIO(
- "/dev/sda5 / ext4 rw,relatime,data=ordered 0 0")
-
- self._driver._mount_quobyte(self.TEST_QUOBYTE_VOLUME,
- self.TEST_MNT_POINT)
-
- mkdir_call = mock.call('mkdir', '-p', self.TEST_MNT_POINT)
-
- mount_call = mock.call(
- 'mount.quobyte', self.TEST_QUOBYTE_VOLUME,
- self.TEST_MNT_POINT, run_as_root=False)
-
- getfattr_call = mock.call(
- 'getfattr', '-n', 'quobyte.info', self.TEST_MNT_POINT,
- run_as_root=False)
-
- mock_execute.assert_has_calls(
- [mkdir_call, mount_call, getfattr_call], any_order=False)
-
- def test_mount_quobyte_already_mounted_detected_seen_in_proc_mount(self):
- with mock.patch.object(self._driver, '_execute') as mock_execute, \
- mock.patch('cinder.volume.drivers.quobyte.QuobyteDriver'
- '.read_proc_mount') as mock_open:
- # Content of /proc/mount (already mounted).
- mock_open.return_value = six.StringIO(
- "quobyte@%s %s fuse rw,nosuid,nodev,noatime,user_id=1000"
- ",group_id=100,default_permissions,allow_other 0 0"
- % (self.TEST_QUOBYTE_VOLUME, self.TEST_MNT_POINT))
-
- self._driver._mount_quobyte(self.TEST_QUOBYTE_VOLUME,
- self.TEST_MNT_POINT)
-
- mock_execute.assert_called_once_with(
- 'getfattr', '-n', 'quobyte.info', self.TEST_MNT_POINT,
- run_as_root=False)
-
- def test_mount_quobyte_should_suppress_and_log_already_mounted_error(self):
- """Based on /proc/mount, the file system is not mounted yet. However,
- mount.quobyte returns with an 'already mounted' error.
- This is a last-resort safe-guard in case /proc/mount parsing was not
- successful.
-
- Because _mount_quobyte gets called with ensure=True, the error will
- be suppressed and logged 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:
- # Content of /proc/mount (empty).
- mock_open.return_value = six.StringIO()
- mock_execute.side_effect = [None, putils.ProcessExecutionError(
- stderr='is busy or already mounted')]
-
- self._driver._mount_quobyte(self.TEST_QUOBYTE_VOLUME,
- self.TEST_MNT_POINT,
- ensure=True)
-
- mkdir_call = mock.call('mkdir', '-p', self.TEST_MNT_POINT)
- mount_call = mock.call(
- 'mount.quobyte', self.TEST_QUOBYTE_VOLUME,
- 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)
-
- def test_mount_quobyte_should_reraise_already_mounted_error(self):
- """Same as
- test_mount_quobyte_should_suppress_and_log_already_mounted_error
- but with ensure=False.
- """
- with mock.patch.object(self._driver, '_execute') as mock_execute, \
- mock.patch('cinder.volume.drivers.quobyte.QuobyteDriver'
- '.read_proc_mount') as mock_open:
- mock_open.return_value = six.StringIO()
- mock_execute.side_effect = [
- None, # mkdir
- putils.ProcessExecutionError( # mount
- stderr='is busy or already mounted')]
-
- self.assertRaises(putils.ProcessExecutionError,
- self._driver._mount_quobyte,
- self.TEST_QUOBYTE_VOLUME,
- self.TEST_MNT_POINT,
- ensure=False)
-
- mkdir_call = mock.call('mkdir', '-p', self.TEST_MNT_POINT)
- mount_call = mock.call(
- 'mount.quobyte', self.TEST_QUOBYTE_VOLUME,
- self.TEST_MNT_POINT, run_as_root=False)
- mock_execute.assert_has_calls([mkdir_call, mount_call],
- any_order=False)
-
- def test_get_hash_str(self):
- """_get_hash_str should calculation correct value."""
- drv = self._driver
-
- self.assertEqual('1331538734b757ed52d0e18c0a7210cd',
- drv._get_hash_str(self.TEST_QUOBYTE_VOLUME))
-
- def test_get_available_capacity_with_df(self):
- """_get_available_capacity should calculate correct value."""
- drv = self._driver
-
- df_total_size = 2620544
- df_avail = 1490560
- df_head = 'Filesystem 1K-blocks Used Available Use% Mounted on\n'
- df_data = 'quobyte@%s %d 996864 %d 41%% %s' % \
- (self.TEST_QUOBYTE_VOLUME, df_total_size, df_avail,
- self.TEST_MNT_POINT)
- df_output = df_head + df_data
-
- drv._get_mount_point_for_share = mock.Mock(return_value=self.
- TEST_MNT_POINT)
-
- drv._execute = mock.Mock(return_value=(df_output, None))
-
- self.assertEqual((df_avail, df_total_size),
- drv._get_available_capacity(self.TEST_QUOBYTE_VOLUME))
- (drv._get_mount_point_for_share.
- assert_called_once_with(self.TEST_QUOBYTE_VOLUME))
- (drv._execute.
- assert_called_once_with('df',
- '--portability',
- '--block-size',
- '1',
- self.TEST_MNT_POINT,
- run_as_root=self._driver._execute_as_root))
-
- def test_get_capacity_info(self):
- with mock.patch.object(self._driver, '_get_available_capacity') \
- as mock_get_available_capacity:
- drv = self._driver
-
- df_size = 2620544
- df_avail = 1490560
-
- mock_get_available_capacity.return_value = (df_avail, df_size)
-
- size, available, used = drv._get_capacity_info(mock.ANY)
-
- mock_get_available_capacity.assert_called_once_with(mock.ANY)
-
- self.assertEqual(df_size, size)
- self.assertEqual(df_avail, available)
- self.assertEqual(size - available, used)
-
- def test_load_shares_config(self):
- """_load_shares_config only puts the Volume URL into shares and strips
- quobyte://.
- """
- drv = self._driver
-
- drv._load_shares_config()
-
- self.assertIn(self.TEST_QUOBYTE_VOLUME_WITHOUT_PROTOCOL, drv.shares)
-
- def test_load_shares_config_without_protocol(self):
- """Same as test_load_shares_config, but this time the URL was specified
- without quobyte:// in front.
- """
- drv = self._driver
-
- drv.configuration.quobyte_volume_url = \
- self.TEST_QUOBYTE_VOLUME_WITHOUT_PROTOCOL
-
- drv._load_shares_config()
-
- self.assertIn(self.TEST_QUOBYTE_VOLUME_WITHOUT_PROTOCOL, drv.shares)
-
- def test_ensure_share_mounted(self):
- """_ensure_share_mounted simple use case."""
- with mock.patch.object(self._driver, '_get_mount_point_for_share') as \
- mock_get_mount_point, \
- mock.patch.object(self._driver, '_mount_quobyte') as \
- mock_mount:
- drv = self._driver
- drv._ensure_share_mounted(self.TEST_QUOBYTE_VOLUME)
-
- mock_get_mount_point.assert_called_once_with(
- self.TEST_QUOBYTE_VOLUME)
- mock_mount.assert_called_once_with(
- self.TEST_QUOBYTE_VOLUME,
- mock_get_mount_point.return_value,
- ensure=True)
-
- def test_ensure_shares_mounted_should_save_mounting_successfully(self):
- """_ensure_shares_mounted should save share if mounted with success."""
- with mock.patch.object(self._driver, '_ensure_share_mounted') \
- as mock_ensure_share_mounted:
- drv = self._driver
-
- drv._ensure_shares_mounted()
-
- mock_ensure_share_mounted.assert_called_once_with(
- self.TEST_QUOBYTE_VOLUME_WITHOUT_PROTOCOL)
- self.assertIn(self.TEST_QUOBYTE_VOLUME_WITHOUT_PROTOCOL,
- drv._mounted_shares)
-
- def test_ensure_shares_mounted_should_not_save_mounting_with_error(self):
- """_ensure_shares_mounted should not save if mount raised an error."""
- with mock.patch.object(self._driver, '_ensure_share_mounted') \
- as mock_ensure_share_mounted:
- drv = self._driver
-
- mock_ensure_share_mounted.side_effect = Exception()
-
- drv._ensure_shares_mounted()
-
- mock_ensure_share_mounted.assert_called_once_with(
- self.TEST_QUOBYTE_VOLUME_WITHOUT_PROTOCOL)
- self.assertEqual(1, len(drv.shares))
- self.assertEqual(0, len(drv._mounted_shares))
-
- def test_do_setup(self):
- """do_setup runs successfully."""
- drv = self._driver
- drv.do_setup(mock.create_autospec(context.RequestContext))
-
- def test_check_for_setup_error_throws_quobyte_volume_url_not_set(self):
- """check_for_setup_error throws if 'quobyte_volume_url' is not set."""
- drv = self._driver
-
- drv.configuration.quobyte_volume_url = None
-
- self.assertRaisesAndMessageMatches(exception.VolumeDriverException,
- 'no Quobyte volume configured',
- drv.check_for_setup_error)
-
- def test_check_for_setup_error_throws_client_not_installed(self):
- """check_for_setup_error throws if client is not installed."""
- drv = self._driver
- drv._execute = mock.Mock(side_effect=OSError
- (errno.ENOENT, 'No such file or directory'))
-
- self.assertRaisesAndMessageMatches(exception.VolumeDriverException,
- 'mount.quobyte is not installed',
- drv.check_for_setup_error)
- drv._execute.assert_called_once_with('mount.quobyte',
- check_exit_code=False,
- run_as_root=False)
-
- def test_check_for_setup_error_throws_client_not_executable(self):
- """check_for_setup_error throws if client cannot be executed."""
- drv = self._driver
-
- drv._execute = mock.Mock(side_effect=OSError
- (errno.EPERM, 'Operation not permitted'))
-
- self.assertRaisesAndMessageMatches(OSError,
- 'Operation not permitted',
- drv.check_for_setup_error)
- drv._execute.assert_called_once_with('mount.quobyte',
- check_exit_code=False,
- run_as_root=False)
-
- def test_find_share_should_throw_error_if_there_is_no_mounted_shares(self):
- """_find_share should throw error if there is no mounted share."""
- drv = self._driver
-
- drv._mounted_shares = []
-
- self.assertRaises(exception.NotFound,
- drv._find_share,
- self.TEST_SIZE_IN_GB)
-
- def test_find_share(self):
- """_find_share simple use case."""
- drv = self._driver
-
- drv._mounted_shares = [self.TEST_QUOBYTE_VOLUME]
-
- self.assertEqual(self.TEST_QUOBYTE_VOLUME,
- drv._find_share(self.TEST_SIZE_IN_GB))
-
- def test_find_share_does_not_throw_error_if_there_isnt_enough_space(self):
- """_find_share intentionally does not throw when df reports no
- available space left.
- """
- with mock.patch.object(self._driver, '_get_available_capacity') \
- as mock_get_available_capacity:
- drv = self._driver
-
- df_size = 2620544
- df_avail = 0
- mock_get_available_capacity.return_value = (df_avail, df_size)
-
- drv._mounted_shares = [self.TEST_QUOBYTE_VOLUME]
-
- self.assertEqual(self.TEST_QUOBYTE_VOLUME,
- drv._find_share(self.TEST_SIZE_IN_GB))
-
- # The current implementation does not call _get_available_capacity.
- # Future ones might do and therefore we mocked it.
- self.assertGreaterEqual(mock_get_available_capacity.call_count, 0)
-
- def _simple_volume(self, uuid=None):
- volume = DumbVolume()
- volume['provider_location'] = self.TEST_QUOBYTE_VOLUME
- if uuid is None:
- volume['id'] = self.VOLUME_UUID
- else:
- volume['id'] = uuid
- # volume['name'] mirrors format from db/sqlalchemy/models.py
- volume['name'] = 'volume-%s' % volume['id']
- volume['size'] = 10
- volume['status'] = 'available'
-
- return volume
-
- def test_create_sparsed_volume(self):
- drv = self._driver
- volume = self._simple_volume()
-
- drv._create_sparsed_file = mock.Mock()
- drv._set_rw_permissions_for_all = mock.Mock()
-
- drv._do_create_volume(volume)
- drv._create_sparsed_file.assert_called_once_with(mock.ANY, mock.ANY)
- drv._set_rw_permissions_for_all.assert_called_once_with(mock.ANY)
-
- def test_create_nonsparsed_volume(self):
- drv = self._driver
- volume = self._simple_volume()
-
- old_value = self._configuration.quobyte_sparsed_volumes
- self._configuration.quobyte_sparsed_volumes = False
-
- drv._create_regular_file = mock.Mock()
- drv._set_rw_permissions_for_all = mock.Mock()
-
- drv._do_create_volume(volume)
- drv._create_regular_file.assert_called_once_with(mock.ANY, mock.ANY)
- drv._set_rw_permissions_for_all.assert_called_once_with(mock.ANY)
-
- self._configuration.quobyte_sparsed_volumes = old_value
-
- def test_create_qcow2_volume(self):
- drv = self._driver
-
- volume = self._simple_volume()
- old_value = self._configuration.quobyte_qcow2_volumes
- self._configuration.quobyte_qcow2_volumes = True
-
- drv._execute = mock.Mock()
-
- hashed = drv._get_hash_str(volume['provider_location'])
- path = '%s/%s/volume-%s' % (self.TEST_MNT_POINT_BASE,
- hashed,
- self.VOLUME_UUID)
-
- drv._do_create_volume(volume)
-
- assert_calls = [mock.call('qemu-img', 'create', '-f', 'qcow2',
- '-o', 'preallocation=metadata', path,
- str(volume['size'] * units.Gi),
- run_as_root=self._driver._execute_as_root),
- mock.call('chmod', 'ugo+rw', path,
- run_as_root=self._driver._execute_as_root)]
- drv._execute.assert_has_calls(assert_calls)
-
- self._configuration.quobyte_qcow2_volumes = old_value
-
- def test_create_volume_should_ensure_quobyte_mounted(self):
- """create_volume ensures shares provided in config are mounted."""
- drv = self._driver
-
- drv.LOG = mock.Mock()
- drv._find_share = mock.Mock()
- drv._do_create_volume = mock.Mock()
- drv._ensure_shares_mounted = mock.Mock()
-
- volume = DumbVolume()
- volume['size'] = self.TEST_SIZE_IN_GB
- drv.create_volume(volume)
-
- drv._find_share.assert_called_once_with(mock.ANY)
- drv._do_create_volume.assert_called_once_with(volume)
- drv._ensure_shares_mounted.assert_called_once_with()
-
- def test_create_volume_should_return_provider_location(self):
- """create_volume should return provider_location with found share."""
- drv = self._driver
-
- drv.LOG = mock.Mock()
- drv._ensure_shares_mounted = mock.Mock()
- drv._do_create_volume = mock.Mock()
- drv._find_share = mock.Mock(return_value=self.TEST_QUOBYTE_VOLUME)
-
- volume = DumbVolume()
- volume['size'] = self.TEST_SIZE_IN_GB
- result = drv.create_volume(volume)
- self.assertEqual(self.TEST_QUOBYTE_VOLUME, result['provider_location'])
-
- drv._do_create_volume.assert_called_once_with(volume)
- drv._ensure_shares_mounted.assert_called_once_with()
- drv._find_share.assert_called_once_with(self.TEST_SIZE_IN_GB)
-
- def test_create_cloned_volume(self):
- drv = self._driver
-
- drv._create_snapshot = mock.Mock()
- drv._copy_volume_from_snapshot = mock.Mock()
- drv._delete_snapshot = mock.Mock()
-
- volume = self._simple_volume()
- src_vref = self._simple_volume()
- src_vref['id'] = '375e32b2-804a-49f2-b282-85d1d5a5b9e1'
- src_vref['name'] = 'volume-%s' % src_vref['id']
- volume_ref = {'id': volume['id'],
- 'name': volume['name'],
- 'status': volume['status'],
- 'provider_location': volume['provider_location'],
- 'size': volume['size']}
-
- snap_ref = {'volume_name': src_vref['name'],
- 'name': 'clone-snap-%s' % src_vref['id'],
- 'size': src_vref['size'],
- 'volume_size': src_vref['size'],
- 'volume_id': src_vref['id'],
- 'id': 'tmp-snap-%s' % src_vref['id'],
- 'volume': src_vref}
-
- drv.create_cloned_volume(volume, src_vref)
-
- drv._create_snapshot.assert_called_once_with(snap_ref)
- drv._copy_volume_from_snapshot.assert_called_once_with(snap_ref,
- volume_ref,
- volume['size'])
- drv._delete_snapshot.assert_called_once_with(mock.ANY)
-
- @mock.patch('cinder.openstack.common.fileutils.delete_if_exists')
- def test_delete_volume(self, mock_delete_if_exists):
- volume = self._simple_volume()
- volume_filename = 'volume-%s' % self.VOLUME_UUID
- volume_path = '%s/%s' % (self.TEST_MNT_POINT, volume_filename)
- info_file = volume_path + '.info'
-
- with mock.patch.object(self._driver, '_ensure_share_mounted') as \
- mock_ensure_share_mounted, \
- mock.patch.object(self._driver, '_local_volume_dir') as \
- mock_local_volume_dir, \
- mock.patch.object(self._driver,
- 'get_active_image_from_info') as \
- mock_active_image_from_info, \
- mock.patch.object(self._driver, '_execute') as \
- mock_execute, \
- mock.patch.object(self._driver, '_local_path_volume') as \
- mock_local_path_volume, \
- mock.patch.object(self._driver, '_local_path_volume_info') as \
- mock_local_path_volume_info:
- mock_local_volume_dir.return_value = self.TEST_MNT_POINT
- mock_active_image_from_info.return_value = volume_filename
- mock_local_path_volume.return_value = volume_path
- mock_local_path_volume_info.return_value = info_file
-
- self._driver.delete_volume(volume)
-
- mock_ensure_share_mounted.assert_called_once_with(
- volume['provider_location'])
- mock_local_volume_dir.assert_called_once_with(volume)
- mock_active_image_from_info.assert_called_once_with(volume)
- mock_execute.assert_called_once_with('rm', '-f', volume_path,
- run_as_root=
- self._driver._execute_as_root)
- mock_local_path_volume_info.assert_called_once_with(volume)
- mock_local_path_volume.assert_called_once_with(volume)
- mock_delete_if_exists.assert_any_call(volume_path)
- mock_delete_if_exists.assert_any_call(info_file)
-
- def test_delete_should_ensure_share_mounted(self):
- """delete_volume should ensure that corresponding share is mounted."""
- drv = self._driver
-
- drv._execute = mock.Mock()
-
- volume = DumbVolume()
- volume['name'] = 'volume-123'
- volume['provider_location'] = self.TEST_QUOBYTE_VOLUME
-
- drv._ensure_share_mounted = mock.Mock()
-
- drv.delete_volume(volume)
-
- (drv._ensure_share_mounted.
- assert_called_once_with(self.TEST_QUOBYTE_VOLUME))
- drv._execute.assert_called_once_with('rm', '-f',
- mock.ANY,
- run_as_root=False)
-
- def test_delete_should_not_delete_if_provider_location_not_provided(self):
- """delete_volume shouldn't delete if provider_location missed."""
- drv = self._driver
-
- drv._ensure_share_mounted = mock.Mock()
- drv._execute = mock.Mock()
-
- volume = DumbVolume()
- volume['name'] = 'volume-123'
- volume['provider_location'] = None
-
- drv.delete_volume(volume)
-
- assert not drv._ensure_share_mounted.called
- assert not drv._execute.called
-
- def test_extend_volume(self):
- drv = self._driver
-
- volume = self._simple_volume()
-
- volume_path = '%s/%s/volume-%s' % (self.TEST_MNT_POINT_BASE,
- drv._get_hash_str(
- self.TEST_QUOBYTE_VOLUME),
- self.VOLUME_UUID)
-
- qemu_img_info_output = """image: volume-%s
- file format: qcow2
- virtual size: 1.0G (1073741824 bytes)
- disk size: 473K
- """ % self.VOLUME_UUID
-
- img_info = imageutils.QemuImgInfo(qemu_img_info_output)
-
- drv.get_active_image_from_info = mock.Mock(return_value=volume['name'])
- image_utils.qemu_img_info = mock.Mock(return_value=img_info)
- image_utils.resize_image = mock.Mock()
-
- drv.extend_volume(volume, 3)
-
- drv.get_active_image_from_info.assert_called_once_with(volume)
- image_utils.qemu_img_info.assert_called_once_with(volume_path)
- image_utils.resize_image.assert_called_once_with(volume_path, 3)
-
- def test_copy_volume_from_snapshot(self):
- drv = self._driver
-
- # lots of test vars to be prepared at first
- dest_volume = self._simple_volume(
- '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 = {'volume_name': src_volume['name'],
- 'name': 'clone-snap-%s' % src_volume['id'],
- 'size': src_volume['size'],
- 'volume_size': src_volume['size'],
- 'volume_id': src_volume['id'],
- 'id': 'tmp-snap-%s' % src_volume['id'],
- 'volume': src_volume}
-
- snap_file = dest_volume['name'] + '.' + snapshot['id']
- snap_path = os.path.join(vol_dir, snap_file)
-
- 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_for_all = 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)
- (image_utils.convert_image.
- assert_called_once_with(src_vol_path,
- 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)
-
- def test_create_volume_from_snapshot_status_not_available(self):
- """Expect an error when the snapshot's status is not 'available'."""
- drv = self._driver
-
- src_volume = self._simple_volume()
- snap_ref = {'volume_name': src_volume['name'],
- 'name': 'clone-snap-%s' % src_volume['id'],
- 'size': src_volume['size'],
- 'volume_size': src_volume['size'],
- 'volume_id': src_volume['id'],
- 'id': 'tmp-snap-%s' % src_volume['id'],
- 'volume': src_volume,
- 'status': 'error'}
-
- new_volume = DumbVolume()
- new_volume['size'] = snap_ref['size']
-
- self.assertRaises(exception.InvalidSnapshot,
- drv.create_volume_from_snapshot,
- new_volume,
- snap_ref)
-
- def test_create_volume_from_snapshot(self):
- drv = self._driver
-
- src_volume = self._simple_volume()
- snap_ref = {'volume_name': src_volume['name'],
- 'name': 'clone-snap-%s' % src_volume['id'],
- 'size': src_volume['size'],
- 'volume_size': src_volume['size'],
- 'volume_id': src_volume['id'],
- 'id': 'tmp-snap-%s' % src_volume['id'],
- 'volume': src_volume,
- 'status': 'available'}
-
- new_volume = DumbVolume()
- new_volume['size'] = snap_ref['size']
-
- drv._ensure_shares_mounted = mock.Mock()
- drv._find_share = mock.Mock(return_value=self.TEST_QUOBYTE_VOLUME)
- drv._do_create_volume = mock.Mock()
- drv._copy_volume_from_snapshot = mock.Mock()
-
- drv.create_volume_from_snapshot(new_volume, snap_ref)
-
- drv._ensure_shares_mounted.assert_called_once_with()
- drv._find_share.assert_called_once_with(new_volume['size'])
- drv._do_create_volume.assert_called_once_with(new_volume)
- (drv._copy_volume_from_snapshot.
- assert_called_once_with(snap_ref, new_volume, new_volume['size']))
-
- def test_initialize_connection(self):
- drv = self._driver
-
- volume = self._simple_volume()
- vol_dir = os.path.join(self.TEST_MNT_POINT_BASE,
- drv._get_hash_str(self.TEST_QUOBYTE_VOLUME))
- vol_path = os.path.join(vol_dir, volume['name'])
-
- qemu_img_output = """image: %s
- file format: raw
- virtual size: 1.0G (1073741824 bytes)
- disk size: 173K
- """ % volume['name']
- img_info = imageutils.QemuImgInfo(qemu_img_output)
-
- drv.get_active_image_from_info = mock.Mock(return_value=volume['name'])
- image_utils.qemu_img_info = mock.Mock(return_value=img_info)
-
- conn_info = drv.initialize_connection(volume, None)
-
- drv.get_active_image_from_info.assert_called_once_with(volume)
- image_utils.qemu_img_info.assert_called_once_with(vol_path)
-
- self.assertEqual(conn_info['data']['format'], 'raw')
- self.assertEqual(conn_info['driver_volume_type'], 'quobyte')
- self.assertEqual(conn_info['data']['name'], volume['name'])
- self.assertEqual(conn_info['mount_point_base'],
- self.TEST_MNT_POINT_BASE)
-
- def test_copy_volume_to_image_raw_image(self):
- drv = self._driver
-
- volume = self._simple_volume()
- volume_path = '%s/%s' % (self.TEST_MNT_POINT, volume['name'])
- image_meta = {'id': '10958016-e196-42e3-9e7f-5d8927ae3099'}
-
- with mock.patch.object(drv, 'get_active_image_from_info') as \
- mock_get_active_image_from_info, \
- mock.patch.object(drv, '_local_volume_dir') as \
- mock_local_volume_dir, \
- mock.patch.object(image_utils, 'qemu_img_info') as \
- mock_qemu_img_info, \
- mock.patch.object(image_utils, 'upload_volume') as \
- mock_upload_volume, \
- mock.patch.object(image_utils, 'create_temporary_file') as \
- mock_create_temporary_file:
- mock_get_active_image_from_info.return_value = volume['name']
-
- mock_local_volume_dir.return_value = self.TEST_MNT_POINT
-
- mock_create_temporary_file.return_value = self.TEST_TMP_FILE
-
- qemu_img_output = """image: %s
- file format: raw
- virtual size: 1.0G (1073741824 bytes)
- disk size: 173K
- """ % volume['name']
- img_info = imageutils.QemuImgInfo(qemu_img_output)
- mock_qemu_img_info.return_value = img_info
-
- upload_path = volume_path
-
- drv.copy_volume_to_image(mock.ANY, volume, mock.ANY, image_meta)
-
- mock_get_active_image_from_info.assert_called_once_with(volume)
- mock_local_volume_dir.assert_called_once_with(volume)
- mock_qemu_img_info.assert_called_once_with(volume_path)
- mock_upload_volume.assert_called_once_with(
- mock.ANY, mock.ANY, mock.ANY, upload_path)
- self.assertTrue(mock_create_temporary_file.called)
-
- def test_copy_volume_to_image_qcow2_image(self):
- """Upload a qcow2 image file which has to be converted to raw first."""
- drv = self._driver
-
- volume = self._simple_volume()
- volume_path = '%s/%s' % (self.TEST_MNT_POINT, volume['name'])
- image_meta = {'id': '10958016-e196-42e3-9e7f-5d8927ae3099'}
-
- with mock.patch.object(drv, 'get_active_image_from_info') as \
- mock_get_active_image_from_info, \
- mock.patch.object(drv, '_local_volume_dir') as \
- mock_local_volume_dir, \
- mock.patch.object(image_utils, 'qemu_img_info') as \
- mock_qemu_img_info, \
- mock.patch.object(image_utils, 'convert_image') as \
- mock_convert_image, \
- mock.patch.object(image_utils, 'upload_volume') as \
- mock_upload_volume, \
- mock.patch.object(image_utils, 'create_temporary_file') as \
- mock_create_temporary_file:
- mock_get_active_image_from_info.return_value = volume['name']
-
- mock_local_volume_dir.return_value = self.TEST_MNT_POINT
-
- mock_create_temporary_file.return_value = self.TEST_TMP_FILE
-
- qemu_img_output = """image: %s
- file format: qcow2
- virtual size: 1.0G (1073741824 bytes)
- disk size: 173K
- """ % volume['name']
- img_info = imageutils.QemuImgInfo(qemu_img_output)
- mock_qemu_img_info.return_value = img_info
-
- upload_path = self.TEST_TMP_FILE
-
- drv.copy_volume_to_image(mock.ANY, volume, mock.ANY, image_meta)
-
- mock_get_active_image_from_info.assert_called_once_with(volume)
- mock_local_volume_dir.assert_called_with(volume)
- mock_qemu_img_info.assert_called_once_with(volume_path)
- mock_convert_image.assert_called_once_with(
- volume_path, upload_path, 'raw')
- mock_upload_volume.assert_called_once_with(
- mock.ANY, mock.ANY, mock.ANY, upload_path)
- self.assertTrue(mock_create_temporary_file.called)
-
- def test_copy_volume_to_image_snapshot_exists(self):
- """Upload an active snapshot which has to be converted to raw first."""
- drv = self._driver
-
- volume = self._simple_volume()
- volume_path = '%s/volume-%s' % (self.TEST_MNT_POINT, self.VOLUME_UUID)
- volume_filename = 'volume-%s' % self.VOLUME_UUID
- image_meta = {'id': '10958016-e196-42e3-9e7f-5d8927ae3099'}
-
- with mock.patch.object(drv, 'get_active_image_from_info') as \
- mock_get_active_image_from_info, \
- mock.patch.object(drv, '_local_volume_dir') as \
- mock_local_volume_dir, \
- mock.patch.object(image_utils, 'qemu_img_info') as \
- mock_qemu_img_info, \
- mock.patch.object(image_utils, 'convert_image') as \
- mock_convert_image, \
- mock.patch.object(image_utils, 'upload_volume') as \
- mock_upload_volume, \
- mock.patch.object(image_utils, 'create_temporary_file') as \
- mock_create_temporary_file:
- mock_get_active_image_from_info.return_value = volume['name']
-
- mock_local_volume_dir.return_value = self.TEST_MNT_POINT
-
- mock_create_temporary_file.return_value = self.TEST_TMP_FILE
-
- qemu_img_output = """image: volume-%s.%s
- file format: qcow2
- virtual size: 1.0G (1073741824 bytes)
- disk size: 173K
- backing file: %s
- """ % (self.VOLUME_UUID, self.SNAP_UUID, volume_filename)
- img_info = imageutils.QemuImgInfo(qemu_img_output)
- mock_qemu_img_info.return_value = img_info
-
- upload_path = self.TEST_TMP_FILE
-
- drv.copy_volume_to_image(mock.ANY, volume, mock.ANY, image_meta)
-
- mock_get_active_image_from_info.assert_called_once_with(volume)
- mock_local_volume_dir.assert_called_with(volume)
- mock_qemu_img_info.assert_called_once_with(volume_path)
- mock_convert_image.assert_called_once_with(
- volume_path, upload_path, 'raw')
- mock_upload_volume.assert_called_once_with(
- mock.ANY, mock.ANY, mock.ANY, upload_path)
- self.assertTrue(mock_create_temporary_file.called)
+++ /dev/null
-# Copyright (c) 2014 Quobyte Inc.
-# 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.
-
-import errno
-import os
-
-from oslo_concurrency import processutils
-from oslo_config import cfg
-from oslo_log import log as logging
-
-from cinder import compute
-from cinder import exception
-from cinder.i18n import _, _LI, _LW
-from cinder.image import image_utils
-from cinder.openstack.common import fileutils
-from cinder import utils
-from cinder.volume.drivers import remotefs as remotefs_drv
-
-VERSION = '1.0'
-
-LOG = logging.getLogger(__name__)
-
-volume_opts = [
- cfg.StrOpt('quobyte_volume_url',
- default=None,
- help=('URL to the Quobyte volume e.g.,'
- ' quobyte://<DIR host>/<volume name>')),
- cfg.StrOpt('quobyte_client_cfg',
- default=None,
- help=('Path to a Quobyte Client configuration file.')),
- cfg.BoolOpt('quobyte_sparsed_volumes',
- default=True,
- help=('Create volumes as sparse files which take no space.'
- ' If set to False, volume is created as regular file.'
- 'In such case volume creation takes a lot of time.')),
- cfg.BoolOpt('quobyte_qcow2_volumes',
- default=True,
- help=('Create volumes as QCOW2 files rather than raw files.')),
- cfg.StrOpt('quobyte_mount_point_base',
- default='$state_path/mnt',
- help=('Base dir containing the mount point'
- ' for the Quobyte volume.')),
-]
-
-CONF = cfg.CONF
-CONF.register_opts(volume_opts)
-
-
-class QuobyteDriver(remotefs_drv.RemoteFSSnapDriver):
- """Cinder driver for Quobyte USP.
-
- Volumes are stored as files on the mounted Quobyte volume. The hypervisor
- will expose them as block devices.
-
- Unlike other similar drivers, this driver uses exactly one Quobyte volume
- because Quobyte USP is a distributed storage system. To add or remove
- capacity, administrators can add or remove storage servers to/from the
- volume.
-
- For different types of volumes e.g., SSD vs. rotating disks,
- use multiple backends in Cinder.
-
- Note: To be compliant with the inherited RemoteFSSnapDriver, Quobyte
- volumes are also referred to as shares.
-
- Version history:
- 1.0 - Initial driver.
- """
-
- driver_volume_type = 'quobyte'
- driver_prefix = 'quobyte'
- volume_backend_name = 'Quobyte'
- VERSION = VERSION
-
- def __init__(self, execute=processutils.execute, *args, **kwargs):
- super(QuobyteDriver, self).__init__(*args, **kwargs)
- self.configuration.append_config_values(volume_opts)
-
- # Used to manage snapshots which are currently attached to a VM.
- self._nova = None
-
- def do_setup(self, context):
- """Any initialization the volume driver does while starting."""
- self.set_nas_security_options(is_new_cinder_install=False)
- super(QuobyteDriver, self).do_setup(context)
-
- self.shares = {} # address : options
- self._nova = compute.API()
-
- def check_for_setup_error(self):
- if not self.configuration.quobyte_volume_url:
- msg = (_("There's no Quobyte volume configured (%s). Example:"
- " quobyte://<DIR host>/<volume name>") %
- 'quobyte_volume_url')
- LOG.warning(msg)
- raise exception.VolumeDriverException(msg)
-
- # Check if mount.quobyte is installed
- try:
- self._execute('mount.quobyte', check_exit_code=False,
- run_as_root=False)
- except OSError as exc:
- if exc.errno == errno.ENOENT:
- raise exception.VolumeDriverException(
- 'mount.quobyte is not installed')
- else:
- raise
-
- def set_nas_security_options(self, is_new_cinder_install):
- self.configuration.nas_secure_file_operations = 'true'
- self.configuration.nas_secure_file_permissions = 'true'
- self._execute_as_root = False
-
- def _qemu_img_info(self, path, volume_name):
- return super(QuobyteDriver, self)._qemu_img_info_base(
- path, volume_name, self.configuration.quobyte_mount_point_base)
-
- @utils.synchronized('quobyte', external=False)
- def create_cloned_volume(self, volume, src_vref):
- """Creates a clone of the specified volume."""
- self._create_cloned_volume(volume, src_vref)
-
- @utils.synchronized('quobyte', external=False)
- def create_volume(self, volume):
- return super(QuobyteDriver, self).create_volume(volume)
-
- @utils.synchronized('quobyte', external=False)
- def create_volume_from_snapshot(self, volume, snapshot):
- return self._create_volume_from_snapshot(volume, snapshot)
-
- 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.
- """
-
- 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'])
- forward_file = snap_info[snapshot['id']]
- forward_path = os.path.join(vol_path, forward_file)
-
- # Find the file which backs this file, which represents the point
- # when this snapshot was created.
- img_info = self._qemu_img_info(forward_path,
- snapshot['volume']['name'])
- path_to_snap_img = os.path.join(vol_path, img_info.backing_file)
-
- path_to_new_vol = self._local_path_volume(volume)
-
- LOG.debug("will copy from snapshot at %s", path_to_snap_img)
-
- if self.configuration.quobyte_qcow2_volumes:
- out_format = 'qcow2'
- 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)
-
- @utils.synchronized('quobyte', external=False)
- def delete_volume(self, volume):
- """Deletes a logical volume."""
-
- if not volume['provider_location']:
- LOG.warning(_LW('Volume %s does not have provider_location '
- 'specified, skipping'), volume['name'])
- return
-
- self._ensure_share_mounted(volume['provider_location'])
-
- volume_dir = self._local_volume_dir(volume)
- mounted_path = os.path.join(volume_dir,
- self.get_active_image_from_info(volume))
-
- self._execute('rm', '-f', mounted_path,
- run_as_root=self._execute_as_root)
-
- # If an exception (e.g. timeout) occurred during delete_snapshot, the
- # base volume may linger around, so just delete it if it exists
- base_volume_path = self._local_path_volume(volume)
- fileutils.delete_if_exists(base_volume_path)
-
- info_path = self._local_path_volume_info(volume)
- fileutils.delete_if_exists(info_path)
-
- @utils.synchronized('quobyte', external=False)
- def create_snapshot(self, snapshot):
- """Apply locking to the create snapshot operation."""
-
- return self._create_snapshot(snapshot)
-
- @utils.synchronized('quobyte', external=False)
- def delete_snapshot(self, snapshot):
- """Apply locking to the delete snapshot operation."""
- self._delete_snapshot(snapshot)
-
- @utils.synchronized('quobyte', external=False)
- def initialize_connection(self, volume, connector):
- """Allow connection to connector and return connection info."""
-
- # Find active qcow2 file
- active_file = self.get_active_image_from_info(volume)
- path = '%s/%s/%s' % (self.configuration.quobyte_mount_point_base,
- self._get_hash_str(volume['provider_location']),
- active_file)
-
- data = {'export': volume['provider_location'],
- 'name': active_file}
- if volume['provider_location'] in self.shares:
- data['options'] = self.shares[volume['provider_location']]
-
- # Test file for raw vs. qcow2 format
- info = self._qemu_img_info(path, volume['name'])
- data['format'] = info.file_format
- if data['format'] not in ['raw', 'qcow2']:
- msg = _('%s must be a valid raw or qcow2 image.') % path
- raise exception.InvalidVolume(msg)
-
- return {
- 'driver_volume_type': 'quobyte',
- 'data': data,
- 'mount_point_base': self.configuration.quobyte_mount_point_base
- }
-
- @utils.synchronized('quobyte', external=False)
- def copy_volume_to_image(self, context, volume, image_service, image_meta):
- self._copy_volume_to_image(context, volume, image_service,
- image_meta)
-
- @utils.synchronized('quobyte', external=False)
- def extend_volume(self, volume, size_gb):
- volume_path = self.local_path(volume)
- volume_filename = os.path.basename(volume_path)
-
- # Ensure no snapshots exist for the volume
- active_image = self.get_active_image_from_info(volume)
- if volume_filename != active_image:
- msg = _('Extend volume is only supported for this'
- ' driver when no snapshots exist.')
- raise exception.InvalidVolume(msg)
-
- info = self._qemu_img_info(volume_path, volume['name'])
- backing_fmt = info.file_format
-
- if backing_fmt not in ['raw', 'qcow2']:
- msg = _('Unrecognized backing format: %s')
- raise exception.InvalidVolume(msg % backing_fmt)
-
- # qemu-img can resize both raw and qcow2 files
- image_utils.resize_image(volume_path, size_gb)
-
- def _do_create_volume(self, volume):
- """Create a volume on given Quobyte volume.
-
- :param volume: volume reference
- """
- volume_path = self.local_path(volume)
- volume_size = volume['size']
-
- if self.configuration.quobyte_qcow2_volumes:
- self._create_qcow2_file(volume_path, volume_size)
- else:
- if self.configuration.quobyte_sparsed_volumes:
- self._create_sparsed_file(volume_path, volume_size)
- else:
- self._create_regular_file(volume_path, volume_size)
-
- self._set_rw_permissions_for_all(volume_path)
-
- def _load_shares_config(self, share_file=None):
- """Put 'quobyte_volume_url' into the 'shares' list.
- :param share_file: string, Not used because the user has to specify the
- the Quobyte volume directly.
- """
- self.shares = {}
-
- url = self.configuration.quobyte_volume_url
-
- # Strip quobyte:// from the URL
- protocol = self.driver_volume_type + "://"
- if url.startswith(protocol):
- url = url[len(protocol):]
-
- self.shares[url] = None # None = No extra mount options.
-
- LOG.debug("Quobyte Volume URL set to: %s", self.shares)
-
- def _ensure_share_mounted(self, quobyte_volume):
- """Mount Quobyte volume.
- :param quobyte_volume: string
- """
- mount_path = self._get_mount_point_for_share(quobyte_volume)
- self._mount_quobyte(quobyte_volume, mount_path, ensure=True)
-
- @utils.synchronized('quobyte_ensure', external=False)
- def _ensure_shares_mounted(self):
- """Mount the Quobyte volume.
-
- Used for example by RemoteFsDriver._update_volume_stats
- """
- self._mounted_shares = []
-
- self._load_shares_config()
-
- for share in self.shares.keys():
- try:
- self._ensure_share_mounted(share)
- self._mounted_shares.append(share)
- except Exception as exc:
- LOG.warning(_LW('Exception during mounting %s'), exc)
-
- LOG.debug('Available shares %s', self._mounted_shares)
-
- def _find_share(self, volume_size_in_gib):
- """Returns the mounted Quobyte volume.
-
- Multiple shares are not supported because the virtualization of
- multiple storage devices is taken care of at the level of Quobyte USP.
-
- For different types of volumes e.g., SSD vs. rotating disks, use
- multiple backends in Cinder.
-
- :param volume_size_in_gib: int size in GB. Ignored by this driver.
- """
-
- if not self._mounted_shares:
- raise exception.NotFound()
-
- assert len(self._mounted_shares) == 1, 'There must be exactly' \
- ' one Quobyte volume.'
- target_volume = self._mounted_shares[0]
-
- LOG.debug('Selected %s as target Quobyte volume.', target_volume)
-
- return target_volume
-
- def _get_mount_point_for_share(self, quobyte_volume):
- """Return mount point for Quobyte volume.
- :param quobyte_volume: Example: storage-host/openstack-volumes
- """
- return os.path.join(self.configuration.quobyte_mount_point_base,
- self._get_hash_str(quobyte_volume))
-
- # open() wrapper to mock reading from /proc/mount.
- @staticmethod
- def read_proc_mount(): # pragma: no cover
- return open('/proc/mounts')
-
- def _mount_quobyte(self, quobyte_volume, mount_path, ensure=False):
- """Mount Quobyte volume to mount path."""
- mounted = False
- for l in QuobyteDriver.read_proc_mount():
- if l.split()[1] == mount_path:
- mounted = True
- break
-
- if mounted:
- try:
- os.stat(mount_path)
- except OSError as exc:
- if exc.errno == errno.ENOTCONN:
- mounted = False
- try:
- LOG.info(_LI('Fixing previous mount %s which was not'
- ' unmounted correctly.'), mount_path)
- self._execute('umount.quobyte', mount_path,
- run_as_root=False)
- except processutils.ProcessExecutionError as exc:
- LOG.warning(_LW("Failed to unmount previous mount: "
- "%s"), exc)
- else:
- # TODO(quobyte): Extend exc analysis in here?
- LOG.warning(_LW("Unknown error occurred while checking "
- "mount point: %s Trying to continue."),
- exc)
-
- if not mounted:
- if not os.path.isdir(mount_path):
- self._execute('mkdir', '-p', mount_path)
-
- command = ['mount.quobyte', quobyte_volume, mount_path]
- if self.configuration.quobyte_client_cfg:
- command.extend(['-c', self.configuration.quobyte_client_cfg])
-
- try:
- LOG.info(_LI('Mounting volume: %s ...'), quobyte_volume)
- self._execute(*command, run_as_root=False)
- LOG.info(_LI('Mounting volume: %s succeeded'), quobyte_volume)
- mounted = True
- except processutils.ProcessExecutionError as exc:
- if ensure and 'already mounted' in exc.stderr:
- LOG.warning(_LW("%s is already mounted"), quobyte_volume)
- else:
- raise
-
- if mounted:
- self._validate_volume(mount_path)
-
- def _validate_volume(self, mount_path):
- """Wraps execute calls for checking validity of a Quobyte volume"""
- command = ['getfattr', "-n", "quobyte.info", mount_path]
- try:
- self._execute(*command, run_as_root=False)
- except processutils.ProcessExecutionError as exc:
- msg = (_("The mount %(mount_path)s is not a valid"
- " Quobyte USP volume. Error: %(exc)s")
- % {'mount_path': mount_path, 'exc': exc})
- raise exception.VolumeDriverException(msg)
-
- if not os.access(mount_path, os.W_OK | os.X_OK):
- LOG.warning(_LW("Volume is not writable. Please broaden the file"
- " permissions. Mount: %s"), mount_path)