Synchronize all LVM operations

This makes us synchronize/serialize all of our external calls to LVM
operations in an attempt to avoid contention of LVM's own locking
scheme, as well as avoid potentially overlapping operations that may
be flushing large amounts of data to disk.

Related-Bug: #1739482
Related-Bug: #1763712
Change-Id: Idb4053685486f2163e7cb135cd579eba5d78f369
This commit is contained in:
Dan Smith 2018-12-12 12:57:04 -08:00
parent 0b70bd7046
commit 206f980cc2
2 changed files with 28 additions and 0 deletions

View File

@ -22,6 +22,7 @@ import os
import re
from os_brick import executor
from oslo_concurrency import lockutils
from oslo_concurrency import processutils as putils
from oslo_log import log as logging
from oslo_utils import excutils
@ -126,6 +127,19 @@ class LVM(executor.Executor):
self.activate_lv(self.vg_thin_pool)
self.pv_list = self.get_all_physical_volumes(root_helper, vg_name)
@lockutils.synchronized('cinder-lvm-exec', external=True)
def _execute(self, *args, **kwargs):
"""Wrap _execute() with a lock.
We want to make sure we are only doing one LVM operation at a time
to avoid contention with the LVM locking system as well as other
potential places where doing these operations in parallel has shown
to cause spurious lockups and hangs. So, we wrap
Executor._execute() with this synchronized decorator to ensure
serialization.
"""
return super(LVM, self)._execute(*args, **kwargs)
def _vg_exists(self):
"""Simple check to see if VG exists.

View File

@ -405,6 +405,20 @@ class BrickLvmTestCase(test.TestCase):
self.vg.activate_lv('my-lv')
@mock.patch('oslo_concurrency.lockutils.lock')
def test_activate_lv_execute_with_lock(self, mock_lock):
with mock.patch('os_brick.executor.Executor._execute') as mock_ex:
self.vg.activate_lv('my-lv')
mock_ex.assert_called()
# NOTE(danms): This is a little icky because it assumes internal
# behavior of oslo_concurrency, but we need to make sure that
# the decorator (which has already run here) is wrapping our
# _execute() method. It calls lock() on __enter__, so we use that
# here to validate that we are synchronized.
mock_lock.assert_called()
mock_lock.return_value.__enter__.assert_called_once_with()
def test_get_mirrored_available_capacity(self):
self.assertEqual(2.0, self.vg.vg_mirror_free_space(1))