Fixes multi-registry config in Quobyte driver
This closes a bug concerning multi-registry configurations for Quobyte volumes due to no longer using the is_mounted() method that failed in that case. Besides, this adds exception handling for the unmount call that is issued on trying to mount an already mounted volume. NOTE: The original commit also added a new feature (fs type based validation) which is omitted in this backport. Closes-Bug: #1737131 Change-Id: Ia5a23ce1123a68608ee2ec6f2ac5dca02da67c59 (cherry picked from commit05a73c0f3a
) (cherry picked from commit656aa1cd40
)
This commit is contained in:
parent
69dd48d777
commit
c958ad8a68
@ -338,6 +338,10 @@ class InvalidVolumeAccessMode(Invalid):
|
||||
msg_fmt = _("Invalid volume access mode: %(access_mode)s")
|
||||
|
||||
|
||||
class StaleVolumeMount(InvalidVolume):
|
||||
msg_fmt = _("The volume mount at %(mount_path)s is unusable.")
|
||||
|
||||
|
||||
class InvalidMetadata(Invalid):
|
||||
msg_fmt = _("Invalid metadata: %(reason)s")
|
||||
|
||||
|
@ -293,13 +293,15 @@ class LibvirtQuobyteVolumeDriverTestCase(
|
||||
test_volume.LibvirtVolumeBaseTestCase):
|
||||
"""Tests the LibvirtQuobyteVolumeDriver class."""
|
||||
|
||||
@mock.patch.object(quobyte, 'umount_volume')
|
||||
@mock.patch.object(quobyte, 'validate_volume')
|
||||
@mock.patch.object(quobyte, 'mount_volume')
|
||||
@mock.patch.object(libvirt_utils, 'is_mounted', return_value=False)
|
||||
def test_libvirt_quobyte_driver_mount(self,
|
||||
mock_is_mounted,
|
||||
mock_mount_volume,
|
||||
mock_validate_volume
|
||||
mock_validate_volume,
|
||||
mock_umount_volume
|
||||
):
|
||||
mnt_base = '/mnt'
|
||||
self.flags(quobyte_mount_point_base=mnt_base, group='libvirt')
|
||||
@ -313,6 +315,9 @@ class LibvirtQuobyteVolumeDriverTestCase(
|
||||
|
||||
connection_info = {'data': {'export': export_string,
|
||||
'name': self.name}}
|
||||
mock_validate_volume.side_effect = [nova_exception.StaleVolumeMount(
|
||||
"This shall fail."), True, True]
|
||||
|
||||
libvirt_driver.connect_volume(connection_info, mock.sentinel.instance)
|
||||
|
||||
conf = libvirt_driver.get_config(connection_info, self.disk_info)
|
||||
@ -324,12 +329,12 @@ class LibvirtQuobyteVolumeDriverTestCase(
|
||||
export_mnt_base,
|
||||
mock.ANY)
|
||||
mock_validate_volume.assert_called_with(export_mnt_base)
|
||||
mock_umount_volume.assert_called_once_with(
|
||||
libvirt_driver._get_mount_path(connection_info))
|
||||
|
||||
@mock.patch.object(quobyte, 'validate_volume')
|
||||
@mock.patch.object(quobyte, 'validate_volume', return_value=True)
|
||||
@mock.patch.object(quobyte, 'umount_volume')
|
||||
@mock.patch.object(libvirt_utils, 'is_mounted', return_value=True)
|
||||
def test_libvirt_quobyte_driver_umount(self, mock_is_mounted,
|
||||
mock_umount_volume,
|
||||
def test_libvirt_quobyte_driver_umount(self, mock_umount_volume,
|
||||
mock_validate_volume):
|
||||
mnt_base = '/mnt'
|
||||
self.flags(quobyte_mount_point_base=mnt_base, group='libvirt')
|
||||
@ -352,7 +357,9 @@ class LibvirtQuobyteVolumeDriverTestCase(
|
||||
libvirt_driver.disconnect_volume(connection_info,
|
||||
mock.sentinel.instance)
|
||||
|
||||
mock_validate_volume.assert_called_once_with(export_mnt_base)
|
||||
mock_validate_volume.assert_has_calls([mock.call(export_mnt_base),
|
||||
mock.call(export_mnt_base),
|
||||
mock.call(export_mnt_base)])
|
||||
mock_umount_volume.assert_called_once_with(export_mnt_base)
|
||||
|
||||
@mock.patch.object(quobyte, 'validate_volume')
|
||||
@ -385,15 +392,14 @@ class LibvirtQuobyteVolumeDriverTestCase(
|
||||
mock.sentinel.instance)
|
||||
|
||||
mock_umount_volume.assert_called_once_with(export_mnt_base)
|
||||
mock_validate_volume.assert_called_once_with(export_mnt_base)
|
||||
mock_validate_volume.assert_has_calls([mock.call(export_mnt_base),
|
||||
mock.call(export_mnt_base)])
|
||||
|
||||
@mock.patch.object(quobyte, 'umount_volume')
|
||||
@mock.patch.object(quobyte, 'validate_volume')
|
||||
@mock.patch.object(quobyte, 'mount_volume')
|
||||
@mock.patch.object(libvirt_utils, 'is_mounted', return_value=False)
|
||||
def test_libvirt_quobyte_driver_qcow2(self, mock_is_mounted,
|
||||
mock_mount_volume,
|
||||
mock_validate_volume
|
||||
):
|
||||
def test_libvirt_quobyte_driver_qcow2(self, mock_mount_volume,
|
||||
mock_validate_volume, mock_umount):
|
||||
mnt_base = '/mnt'
|
||||
self.flags(quobyte_mount_point_base=mnt_base, group='libvirt')
|
||||
libvirt_driver = quobyte.LibvirtQuobyteVolumeDriver(self.fake_host)
|
||||
@ -408,6 +414,8 @@ class LibvirtQuobyteVolumeDriverTestCase(
|
||||
|
||||
export_mnt_base = os.path.join(mnt_base,
|
||||
utils.get_hash_str(quobyte_volume))
|
||||
mock_validate_volume.side_effect = [nova_exception.StaleVolumeMount(
|
||||
"This shall fail."), True, True]
|
||||
|
||||
libvirt_driver.connect_volume(connection_info, mock.sentinel.instance)
|
||||
conf = libvirt_driver.get_config(connection_info, self.disk_info)
|
||||
@ -423,6 +431,8 @@ class LibvirtQuobyteVolumeDriverTestCase(
|
||||
|
||||
libvirt_driver.disconnect_volume(connection_info,
|
||||
mock.sentinel.instance)
|
||||
mock_umount.assert_has_calls([mock.call(export_mnt_base),
|
||||
mock.call(export_mnt_base)])
|
||||
|
||||
@mock.patch.object(libvirt_utils, 'is_mounted', return_value=True)
|
||||
def test_libvirt_quobyte_driver_mount_non_quobyte_volume(self,
|
||||
|
@ -13,7 +13,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import errno
|
||||
import os
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
@ -26,7 +25,6 @@ import nova.conf
|
||||
from nova import exception as nova_exception
|
||||
from nova.i18n import _
|
||||
from nova import utils
|
||||
from nova.virt.libvirt import utils as libvirt_utils
|
||||
from nova.virt.libvirt.volume import fs
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -75,7 +73,10 @@ def umount_volume(mnt_base):
|
||||
|
||||
|
||||
def validate_volume(mount_path):
|
||||
"""Runs a number of tests to be sure this is a (working) Quobyte mount"""
|
||||
"""Determine if the volume is a valid Quobyte mount.
|
||||
|
||||
Runs a number of tests to be sure this is a (working) Quobyte mount
|
||||
"""
|
||||
partitions = psutil.disk_partitions(all=True)
|
||||
for p in partitions:
|
||||
if mount_path != p.mountpoint:
|
||||
@ -90,10 +91,10 @@ def validate_volume(mount_path):
|
||||
msg = (_("The mount %(mount_path)s is not a "
|
||||
"valid Quobyte volume. Stale mount?")
|
||||
% {'mount_path': mount_path})
|
||||
raise nova_exception.InvalidVolume(msg)
|
||||
raise nova_exception.StaleVolumeMount(msg, mount_path=mount_path)
|
||||
else:
|
||||
msg = (_("The mount %(mount_path)s is not a valid"
|
||||
" Quobyte volume according to partition list.")
|
||||
msg = (_("The mount %(mount_path)s is not a valid "
|
||||
"Quobyte volume according to partition list.")
|
||||
% {'mount_path': mount_path})
|
||||
raise nova_exception.InvalidVolume(msg)
|
||||
msg = (_("No matching Quobyte mount entry for %(mount_path)s"
|
||||
@ -128,39 +129,40 @@ class LibvirtQuobyteVolumeDriver(fs.LibvirtBaseFileSystemVolumeDriver):
|
||||
data = connection_info['data']
|
||||
quobyte_volume = self._normalize_export(data['export'])
|
||||
mount_path = self._get_mount_path(connection_info)
|
||||
mounted = libvirt_utils.is_mounted(mount_path,
|
||||
SOURCE_PROTOCOL
|
||||
+ '@' + quobyte_volume)
|
||||
if mounted:
|
||||
try:
|
||||
os.stat(mount_path)
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.ENOTCONN:
|
||||
mounted = False
|
||||
LOG.info('Fixing previous mount %s which was not'
|
||||
' unmounted correctly.', mount_path)
|
||||
umount_volume(mount_path)
|
||||
try:
|
||||
validate_volume(mount_path)
|
||||
mounted = True
|
||||
except nova_exception.StaleVolumeMount:
|
||||
mounted = False
|
||||
LOG.info('Fixing previous mount %s which was not '
|
||||
'unmounted correctly.', mount_path)
|
||||
umount_volume(mount_path)
|
||||
except nova_exception.InvalidVolume:
|
||||
mounted = False
|
||||
|
||||
if not mounted:
|
||||
mount_volume(quobyte_volume,
|
||||
mount_path,
|
||||
CONF.libvirt.quobyte_client_cfg)
|
||||
|
||||
validate_volume(mount_path)
|
||||
try:
|
||||
validate_volume(mount_path)
|
||||
except (nova_exception.InvalidVolume,
|
||||
nova_exception.StaleVolumeMount) as nex:
|
||||
LOG.error("Could not mount Quobyte volume: %s", nex)
|
||||
|
||||
@utils.synchronized('connect_qb_volume')
|
||||
def disconnect_volume(self, connection_info, instance):
|
||||
"""Disconnect the volume."""
|
||||
|
||||
quobyte_volume = self._normalize_export(
|
||||
connection_info['data']['export'])
|
||||
mount_path = self._get_mount_path(connection_info)
|
||||
|
||||
if libvirt_utils.is_mounted(mount_path, 'quobyte@' + quobyte_volume):
|
||||
umount_volume(mount_path)
|
||||
try:
|
||||
validate_volume(mount_path)
|
||||
except (nova_exception.InvalidVolume,
|
||||
nova_exception.StaleVolumeMount) as exc:
|
||||
LOG.warning("Could not disconnect Quobyte volume mount: %s", exc)
|
||||
else:
|
||||
LOG.info("Trying to disconnected unmounted volume at %s",
|
||||
mount_path)
|
||||
umount_volume(mount_path)
|
||||
|
||||
def _normalize_export(self, export):
|
||||
protocol = SOURCE_PROTOCOL + "://"
|
||||
|
5
releasenotes/notes/qb-bug-1730933-6695470ebaee0fbd.yaml
Normal file
5
releasenotes/notes/qb-bug-1730933-6695470ebaee0fbd.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
Fixes a bug that caused Nova to fail on mounting Quobyte volumes
|
||||
whose volume URL contained multiple registries.
|
Loading…
Reference in New Issue
Block a user