]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
SMBFS: Lock on a per-volume basis
authorAlessandro Pilotti <apilotti@cloudbasesolutions.com>
Wed, 1 Apr 2015 17:57:14 +0000 (20:57 +0300)
committerLucian Petrut <lpetrut@cloudbasesolutions.com>
Thu, 16 Apr 2015 10:51:43 +0000 (13:51 +0300)
This allows operations that do not conflict with each
other (i.e. are on different volumes) to run concurrently.
The prior locking scheme was too coarse and essentially
made the driver single-threaded.

This patch moves the implementation of the GlusterFS driver locking
scheme to the RemoteFS base driver so that other similar volume
drivers can use it.

Closes-Bug: #1439352
Change-Id: I37be14e7525406e0ad568f6228c1303998c5235f

cinder/tests/test_remotefs.py
cinder/volume/drivers/glusterfs.py
cinder/volume/drivers/remotefs.py
cinder/volume/drivers/smbfs.py

index 3dc31903ae8b1f49f446300397cdf9d542edd14e..a17723d2dc84eeb89ad5b9a00a4c7bae66d61579 100644 (file)
@@ -19,14 +19,16 @@ import mock
 
 from cinder import exception
 from cinder import test
+from cinder import utils
 from cinder.volume.drivers import remotefs
 
 
 class RemoteFsSnapDriverTestCase(test.TestCase):
 
     _FAKE_CONTEXT = 'fake_context'
-    _FAKE_VOLUME_NAME = 'volume-4f711859-4928-4cb7-801a-a50c37ceaccc'
-    _FAKE_VOLUME = {'id': '4f711859-4928-4cb7-801a-a50c37ceaccc',
+    _FAKE_VOLUME_ID = '4f711859-4928-4cb7-801a-a50c37ceaccc'
+    _FAKE_VOLUME_NAME = 'volume-%s' % _FAKE_VOLUME_ID
+    _FAKE_VOLUME = {'id': _FAKE_VOLUME_ID,
                     'size': 1,
                     'provider_location': 'fake_share',
                     'name': _FAKE_VOLUME_NAME,
@@ -295,3 +297,56 @@ class RemoteFsSnapDriverTestCase(test.TestCase):
         self.assertRaises(exception.InvalidVolume,
                           self._driver._create_snapshot,
                           fake_snapshot)
+
+    @mock.patch.object(utils, 'synchronized')
+    def _locked_volume_operation_test_helper(self, mock_synchronized, func,
+                                             expected_exception=False,
+                                             *args, **kwargs):
+        def mock_decorator(*args, **kwargs):
+            def mock_inner(f):
+                return f
+            return mock_inner
+
+        mock_synchronized.side_effect = mock_decorator
+        expected_lock = '%s-%s' % (self._driver.driver_prefix,
+                                   self._FAKE_VOLUME_ID)
+
+        if expected_exception:
+            self.assertRaises(expected_exception, func,
+                              self._driver,
+                              *args, **kwargs)
+        else:
+            ret_val = func(self._driver, *args, **kwargs)
+
+            mock_synchronized.assert_called_with(expected_lock,
+                                                 external=False)
+            self.assertEqual(mock.sentinel.ret_val, ret_val)
+
+    def test_locked_volume_id_operation(self):
+        mock_volume = {'id': self._FAKE_VOLUME_ID}
+
+        @remotefs.locked_volume_id_operation
+        def synchronized_func(inst, volume):
+            return mock.sentinel.ret_val
+
+        self._locked_volume_operation_test_helper(func=synchronized_func,
+                                                  volume=mock_volume)
+
+    def test_locked_volume_id_snapshot_operation(self):
+        mock_snapshot = {'volume': {'id': self._FAKE_VOLUME_ID}}
+
+        @remotefs.locked_volume_id_operation
+        def synchronized_func(inst, snapshot):
+            return mock.sentinel.ret_val
+
+        self._locked_volume_operation_test_helper(func=synchronized_func,
+                                                  snapshot=mock_snapshot)
+
+    def test_locked_volume_id_operation_exception(self):
+        @remotefs.locked_volume_id_operation
+        def synchronized_func(inst):
+            return mock.sentinel.ret_val
+
+        self._locked_volume_operation_test_helper(
+            func=synchronized_func,
+            expected_exception=exception.VolumeBackendAPIException)
index 01853ac54032467cf10896d7e998d0bdd83b3813..325c7d2214506e3ad666785dd77efd612e03d4b9 100644 (file)
@@ -52,48 +52,6 @@ volume_opts = [
 CONF = cfg.CONF
 CONF.register_opts(volume_opts)
 
-lock_tag = 'glusterfs'
-
-
-def locked_volume_id_operation(f, external=False):
-    """Lock decorator for volume operations.
-
-       Takes a named lock prior to executing the operation. The lock is named
-       with the id of the volume. This lock can then be used
-       by other operations to avoid operation conflicts on shared volumes.
-
-       May be applied to methods of signature:
-          method(<self>, volume, *, **)
-    """
-
-    def lvo_inner1(inst, volume, *args, **kwargs):
-        @utils.synchronized('%s-%s' % (lock_tag, volume['id']),
-                            external=external)
-        def lvo_inner2(*_args, **_kwargs):
-            return f(*_args, **_kwargs)
-        return lvo_inner2(inst, volume, *args, **kwargs)
-    return lvo_inner1
-
-
-def locked_volume_id_snapshot_operation(f, external=False):
-    """Lock decorator for volume operations that use snapshot objects.
-
-       Takes a named lock prior to executing the operation. The lock is named
-       with the id of the volume. This lock can then be used
-       by other operations to avoid operation conflicts on shared volumes.
-
-       May be applied to methods of signature:
-          method(<self>, snapshot, *, **)
-    """
-
-    def lso_inner1(inst, snapshot, *args, **kwargs):
-        @utils.synchronized('%s-%s' % (lock_tag, snapshot['volume']['id']),
-                            external=external)
-        def lso_inner2(*_args, **_kwargs):
-            return f(*_args, **_kwargs)
-        return lso_inner2(inst, snapshot, *args, **kwargs)
-    return lso_inner1
-
 
 class GlusterfsDriver(remotefs_drv.RemoteFSSnapDriver):
     """Gluster based cinder driver. Creates file on Gluster share for using it
@@ -202,12 +160,7 @@ class GlusterfsDriver(remotefs_drv.RemoteFSSnapDriver):
                           hashed)
         return path
 
-    @locked_volume_id_operation
-    def create_cloned_volume(self, volume, src_vref):
-        """Creates a clone of the specified volume."""
-        self._create_cloned_volume(volume, src_vref)
-
-    @locked_volume_id_operation
+    @remotefs_drv.locked_volume_id_operation
     def create_volume(self, volume):
         """Creates a volume."""
 
@@ -221,10 +174,6 @@ class GlusterfsDriver(remotefs_drv.RemoteFSSnapDriver):
 
         return {'provider_location': volume['provider_location']}
 
-    @locked_volume_id_operation
-    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.
 
@@ -265,7 +214,7 @@ class GlusterfsDriver(remotefs_drv.RemoteFSSnapDriver):
 
         self._set_rw_permissions_for_all(path_to_new_vol)
 
-    @locked_volume_id_operation
+    @remotefs_drv.locked_volume_id_operation
     def delete_volume(self, volume):
         """Deletes a logical volume."""
 
@@ -291,21 +240,10 @@ class GlusterfsDriver(remotefs_drv.RemoteFSSnapDriver):
         info_path = self._local_path_volume_info(volume)
         fileutils.delete_if_exists(info_path)
 
-    @locked_volume_id_snapshot_operation
-    def create_snapshot(self, snapshot):
-        """Apply locking to the create snapshot operation."""
-
-        return self._create_snapshot(snapshot)
-
     def _get_matching_backing_file(self, backing_chain, snapshot_file):
         return next(f for f in backing_chain
                     if f.get('backing-filename', '') == snapshot_file)
 
-    @locked_volume_id_snapshot_operation
-    def delete_snapshot(self, snapshot):
-        """Apply locking to the delete snapshot operation."""
-        self._delete_snapshot(snapshot)
-
     def ensure_export(self, ctx, volume):
         """Synchronously recreates an export for a logical volume."""
 
@@ -323,7 +261,7 @@ class GlusterfsDriver(remotefs_drv.RemoteFSSnapDriver):
     def validate_connector(self, connector):
         pass
 
-    @locked_volume_id_operation
+    @remotefs_drv.locked_volume_id_operation
     def initialize_connection(self, volume, connector):
         """Allow connection to connector and return connection info."""
 
@@ -355,29 +293,7 @@ class GlusterfsDriver(remotefs_drv.RemoteFSSnapDriver):
         """Disallow connection from connector."""
         pass
 
-    def copy_volume_to_image(self, context, volume, image_service, image_meta):
-        """Copy the volume to the specified image.
-
-           Warning: parameter order is non-standard to assist with locking
-           decorators.
-        """
-
-        return self._copy_volume_to_image_with_lock(volume,
-                                                    context,
-                                                    image_service,
-                                                    image_meta)
-
-    @locked_volume_id_operation
-    def _copy_volume_to_image_with_lock(self, volume, context,
-                                        image_service, image_meta):
-        """Call private method for this, which handles per-volume locking."""
-
-        return self._copy_volume_to_image(context,
-                                          volume,
-                                          image_service,
-                                          image_meta)
-
-    @locked_volume_id_operation
+    @remotefs_drv.locked_volume_id_operation
     def extend_volume(self, volume, size_gb):
         volume_path = self.local_path(volume)
         volume_filename = os.path.basename(volume_path)
index c9655feb78b067aaede7c46e9853a87cc1cffd17..30e9feddf0b1a149cd486c428a105c7d74570faf 100644 (file)
@@ -15,6 +15,7 @@
 #    under the License.
 
 import hashlib
+import inspect
 import json
 import os
 import re
@@ -29,6 +30,7 @@ from oslo_utils import units
 from cinder import compute
 from cinder import db
 from cinder import exception
+from cinder import utils
 from cinder.i18n import _, _LE, _LI, _LW
 from cinder.image import image_utils
 from cinder.volume import driver
@@ -87,11 +89,43 @@ CONF = cfg.CONF
 CONF.register_opts(nas_opts)
 
 
+def locked_volume_id_operation(f, external=False):
+    """Lock decorator for volume operations.
+
+       Takes a named lock prior to executing the operation. The lock is named
+       with the id of the volume. This lock can then be used
+       by other operations to avoid operation conflicts on shared volumes.
+
+       May be applied to methods of signature:
+          method(<self>, volume, *, **)
+    """
+
+    def lvo_inner1(inst, *args, **kwargs):
+        lock_tag = inst.driver_prefix
+        call_args = inspect.getcallargs(f, inst, *args, **kwargs)
+
+        if call_args.get('volume'):
+            volume_id = call_args['volume']['id']
+        elif call_args.get('snapshot'):
+            volume_id = call_args['snapshot']['volume']['id']
+        else:
+            err_msg = _('The decorated method must accept either a volume or '
+                        'a snapshot object')
+            raise exception.VolumeBackendAPIException(data=err_msg)
+
+        @utils.synchronized('%s-%s' % (lock_tag, volume_id),
+                            external=external)
+        def lvo_inner2():
+            return f(inst, *args, **kwargs)
+        return lvo_inner2()
+    return lvo_inner1
+
+
 class RemoteFSDriver(driver.VolumeDriver):
     """Common base for drivers that work like NFS."""
 
     driver_volume_type = None
-    driver_prefix = None
+    driver_prefix = 'remotefs'
     volume_backend_name = None
     SHARE_FORMAT_REGEX = r'.+:/.+'
 
@@ -1368,3 +1402,33 @@ class RemoteFSSnapDriver(RemoteFSDriver):
         path_to_delete = os.path.join(
             self._local_volume_dir(snapshot['volume']), file_to_delete)
         self._execute('rm', '-f', path_to_delete, run_as_root=True)
+
+    @locked_volume_id_operation
+    def create_snapshot(self, snapshot):
+        """Apply locking to the create snapshot operation."""
+
+        return self._create_snapshot(snapshot)
+
+    @locked_volume_id_operation
+    def delete_snapshot(self, snapshot):
+        """Apply locking to the delete snapshot operation."""
+
+        return self._delete_snapshot(snapshot)
+
+    @locked_volume_id_operation
+    def create_volume_from_snapshot(self, volume, snapshot):
+        return self._create_volume_from_snapshot(volume, snapshot)
+
+    @locked_volume_id_operation
+    def create_cloned_volume(self, volume, src_vref):
+        """Creates a clone of the specified volume."""
+        return self._create_cloned_volume(volume, src_vref)
+
+    @locked_volume_id_operation
+    def copy_volume_to_image(self, context, volume, image_service, image_meta):
+        """Copy the volume to the specified image."""
+
+        return self._copy_volume_to_image(context,
+                                          volume,
+                                          image_service,
+                                          image_meta)
index eb4a54dd9617b509552ac73b6d1a6dccfaba080c..9191b197f9ae6419ec2ef9ec6d8d17a894ff6d44 100644 (file)
@@ -105,6 +105,7 @@ class SmbfsDriver(remotefs_drv.RemoteFSSnapDriver):
         return super(SmbfsDriver, self)._qemu_img_info_base(
             path, volume_name, self.configuration.smbfs_mount_point_base)
 
+    @remotefs_drv.locked_volume_id_operation
     def initialize_connection(self, volume, connector):
         """Allow connection to connector and return connection info.
 
@@ -220,7 +221,7 @@ class SmbfsDriver(remotefs_drv.RemoteFSSnapDriver):
 
         return volume_format
 
-    @utils.synchronized('smbfs', external=False)
+    @remotefs_drv.locked_volume_id_operation
     def delete_volume(self, volume):
         """Deletes a logical volume."""
         if not volume['provider_location']:
@@ -387,12 +388,6 @@ class SmbfsDriver(remotefs_drv.RemoteFSSnapDriver):
             return False
         return True
 
-    @utils.synchronized('smbfs', external=False)
-    def create_snapshot(self, snapshot):
-        """Apply locking to the create snapshot operation."""
-
-        return self._create_snapshot(snapshot)
-
     def _create_snapshot_online(self, snapshot, backing_filename,
                                 new_snap_path):
         msg = _("This driver does not support snapshotting in-use volumes.")
@@ -415,13 +410,7 @@ class SmbfsDriver(remotefs_drv.RemoteFSSnapDriver):
                         "format: %s") % volume_format
             raise exception.InvalidVolume(err_msg)
 
-    @utils.synchronized('smbfs', external=False)
-    def delete_snapshot(self, snapshot):
-        """Apply locking to the delete snapshot operation."""
-
-        return self._delete_snapshot(snapshot)
-
-    @utils.synchronized('smbfs', external=False)
+    @remotefs_drv.locked_volume_id_operation
     def extend_volume(self, volume, size_gb):
         LOG.info(_LI('Extending volume %s.'), volume['id'])
         self._extend_volume(volume, size_gb)
@@ -473,14 +462,6 @@ class SmbfsDriver(remotefs_drv.RemoteFSSnapDriver):
                                               'extend volume %s to %sG.'
                                               % (volume['id'], size_gb))
 
-    @utils.synchronized('smbfs', 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('smbfs', 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.
 
@@ -548,11 +529,6 @@ class SmbfsDriver(remotefs_drv.RemoteFSSnapDriver):
                 reason=(_("Expected volume size was %d") % volume['size'])
                 + (_(" but size is now %d.") % virt_size))
 
-    @utils.synchronized('smbfs', external=False)
-    def create_cloned_volume(self, volume, src_vref):
-        """Creates a clone of the specified volume."""
-        return self._create_cloned_volume(volume, src_vref)
-
     def _ensure_share_mounted(self, smbfs_share):
         mnt_flags = []
         if self.shares.get(smbfs_share) is not None: