]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Revert "Revert First version of Cinder driver for Quobyte"
authorMike Perez <thingee@gmail.com>
Mon, 3 Aug 2015 15:17:18 +0000 (08:17 -0700)
committerMike Perez <thingee@gmail.com>
Wed, 5 Aug 2015 22:39:54 +0000 (15:39 -0700)
This reverts commit e896ae29eccc8575aca08e4ebeb27a82e28fa8eb.

Change-Id: Id6b25af58434ecef891c2deecc1a574d351ee713

cinder/tests/unit/test_quobyte.py [new file with mode: 0644]
cinder/volume/drivers/quobyte.py [new file with mode: 0644]

diff --git a/cinder/tests/unit/test_quobyte.py b/cinder/tests/unit/test_quobyte.py
new file mode 100644 (file)
index 0000000..7ff3910
--- /dev/null
@@ -0,0 +1,933 @@
+# 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. """
+
+        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):
+        """test_mount_quobyte_should_suppress_and_log_already_mounted_error
+
+           Based on /proc/mount, the file system is not mounted yet. However,
+           mount.quobyte returns with an 'already mounted' error. This is
+           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):
+        """test_mount_quobyte_should_reraise_already_mounted_error
+
+        Like 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 takes the Volume URL 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 URL is without quobyte://."""
+        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 no space is 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
new file mode 100644 (file)
index 0000000..2b27604
--- /dev/null
@@ -0,0 +1,439 @@
+# 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)