From 37e45ad44c22a5b9346bece8279cde3691562e52 Mon Sep 17 00:00:00 2001 From: Silvan Kaiser Date: Wed, 22 Nov 2017 14:35:11 +0100 Subject: [PATCH] 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 commit 05a73c0f3a9f8edf9024f9870279bc6fb7bba2e7) (cherry picked from commit 656aa1cd40570154df606484d31616989b5296aa) (cherry picked from commit c958ad8a68ea9a8ea465d9e3dd889248d9f42481) --- nova/exception.py | 4 ++ .../unit/virt/libvirt/volume/test_quobyte.py | 34 +++++++----- nova/virt/libvirt/volume/quobyte.py | 54 ++++++++++--------- .../qb-bug-1730933-6695470ebaee0fbd.yaml | 5 ++ 4 files changed, 59 insertions(+), 38 deletions(-) create mode 100644 releasenotes/notes/qb-bug-1730933-6695470ebaee0fbd.yaml diff --git a/nova/exception.py b/nova/exception.py index badafe5cc09d..fe492b4328d1 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -342,6 +342,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") diff --git a/nova/tests/unit/virt/libvirt/volume/test_quobyte.py b/nova/tests/unit/virt/libvirt/volume/test_quobyte.py index 58265b4e4f8a..fb693cb07d8e 100644 --- a/nova/tests/unit/virt/libvirt/volume/test_quobyte.py +++ b/nova/tests/unit/virt/libvirt/volume/test_quobyte.py @@ -294,13 +294,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') @@ -314,6 +316,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) @@ -325,12 +330,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') @@ -353,7 +358,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') @@ -386,15 +393,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) @@ -409,6 +415,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) @@ -424,6 +432,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, diff --git a/nova/virt/libvirt/volume/quobyte.py b/nova/virt/libvirt/volume/quobyte.py index 7641bfc6e392..377db48d81af 100644 --- a/nova/virt/libvirt/volume/quobyte.py +++ b/nova/virt/libvirt/volume/quobyte.py @@ -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 + "://" diff --git a/releasenotes/notes/qb-bug-1730933-6695470ebaee0fbd.yaml b/releasenotes/notes/qb-bug-1730933-6695470ebaee0fbd.yaml new file mode 100644 index 000000000000..f1e98d7a0422 --- /dev/null +++ b/releasenotes/notes/qb-bug-1730933-6695470ebaee0fbd.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixes a bug that caused Nova to fail on mounting Quobyte volumes + whose volume URL contained multiple registries.