From: Duncan Thomas Date: Wed, 24 Jun 2015 15:53:47 +0000 (+0300) Subject: Revert First version of Cinder driver for Quobyte X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=e896ae29eccc8575aca08e4ebeb27a82e28fa8eb;p=openstack-build%2Fcinder-build.git Revert First version of Cinder driver for Quobyte This reverts commit 49d92764183e288b8f62b91a51179c307dd19a44. CI hasn't passed in its last 60 runs and hasn't ran in the last 73 days. UpgradeImpact: QuoByte is not supported in the Liberty release Change-Id: Ibe1c4afddc9856965b28a139c1d37f939578577b --- diff --git a/cinder/tests/unit/test_quobyte.py b/cinder/tests/unit/test_quobyte.py deleted file mode 100644 index 9635cf91a..000000000 --- a/cinder/tests/unit/test_quobyte.py +++ /dev/null @@ -1,938 +0,0 @@ -# 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) diff --git a/cinder/volume/drivers/quobyte.py b/cinder/volume/drivers/quobyte.py deleted file mode 100644 index 49453568c..000000000 --- a/cinder/volume/drivers/quobyte.py +++ /dev/null @@ -1,436 +0,0 @@ -# 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:///')), - 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:///") % - '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)