Skip to content

Commit 05a73c0

Browse files
committed
Added mount fstype based validation of Quobyte mounts
The validation of Quobyte mounts is extended to validate based on a mounts file system type being set to "fuse.quobyte". This includes adding a new StaleVolumeMount exception type to the Nova exceptions. This also closes a bug concerning multi-registry configurations for Quobyte volumes due to no longer using the is_mounted() method that failed in that case. Finally this adds exception handling for the unmount call that is issued on trying to mount an already mounted volume. Closes-Bug: #1730933 Closes-Bug: #1737131 Change-Id: Ia5a23ce1123a68608ee2ec6f2ac5dca02da67c59
1 parent 104d79d commit 05a73c0

File tree

4 files changed

+88
-40
lines changed

4 files changed

+88
-40
lines changed

nova/exception.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,10 @@ class InvalidVolumeAccessMode(Invalid):
354354
msg_fmt = _("Invalid volume access mode: %(access_mode)s")
355355

356356

357+
class StaleVolumeMount(InvalidVolume):
358+
msg_fmt = _("The volume mount at %(mount_path)s is unusable.")
359+
360+
357361
class InvalidMetadata(Invalid):
358362
msg_fmt = _("Invalid metadata: %(reason)s")
359363

nova/tests/unit/virt/libvirt/volume/test_quobyte.py

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def test_quobyte_umount_volume_fails(self,
203203

204204
@mock.patch.object(psutil, "disk_partitions")
205205
@mock.patch.object(os, "stat")
206-
def test_validate_volume_all_good(self, stat_mock, part_mock):
206+
def test_validate_volume_all_good_prefix_val(self, stat_mock, part_mock):
207207
part_mock.return_value = self.get_mock_partitions()
208208
drv = quobyte
209209

@@ -220,6 +220,27 @@ def statMockCall(*args):
220220
stat_mock.assert_called_once_with(self.TEST_MNT_POINT)
221221
part_mock.assert_called_once_with(all=True)
222222

223+
@mock.patch.object(psutil, "disk_partitions")
224+
@mock.patch.object(os, "stat")
225+
def test_validate_volume_all_good_fs_type(self, stat_mock, part_mock):
226+
part_mock.return_value = self.get_mock_partitions()
227+
part_mock.return_value[0].device = "not_quobyte"
228+
part_mock.return_value[0].fstype = "fuse.quobyte"
229+
drv = quobyte
230+
231+
def statMockCall(*args):
232+
if args[0] == self.TEST_MNT_POINT:
233+
stat_result = mock.Mock()
234+
stat_result.st_size = 0
235+
return stat_result
236+
return os.stat(args)
237+
stat_mock.side_effect = statMockCall
238+
239+
drv.validate_volume(self.TEST_MNT_POINT)
240+
241+
stat_mock.assert_called_once_with(self.TEST_MNT_POINT)
242+
part_mock.assert_called_once_with(all=True)
243+
223244
@mock.patch.object(psutil, "disk_partitions")
224245
@mock.patch.object(os, "stat")
225246
def test_validate_volume_mount_not_working(self, stat_mock, part_mock):
@@ -293,13 +314,15 @@ class LibvirtQuobyteVolumeDriverTestCase(
293314
test_volume.LibvirtVolumeBaseTestCase):
294315
"""Tests the LibvirtQuobyteVolumeDriver class."""
295316

317+
@mock.patch.object(quobyte, 'umount_volume')
296318
@mock.patch.object(quobyte, 'validate_volume')
297319
@mock.patch.object(quobyte, 'mount_volume')
298320
@mock.patch.object(libvirt_utils, 'is_mounted', return_value=False)
299321
def test_libvirt_quobyte_driver_mount(self,
300322
mock_is_mounted,
301323
mock_mount_volume,
302-
mock_validate_volume
324+
mock_validate_volume,
325+
mock_umount_volume
303326
):
304327
mnt_base = '/mnt'
305328
self.flags(quobyte_mount_point_base=mnt_base, group='libvirt')
@@ -313,6 +336,9 @@ def test_libvirt_quobyte_driver_mount(self,
313336

314337
connection_info = {'data': {'export': export_string,
315338
'name': self.name}}
339+
mock_validate_volume.side_effect = [nova_exception.StaleVolumeMount(
340+
"This shall fail."), True, True]
341+
316342
libvirt_driver.connect_volume(connection_info, mock.sentinel.instance)
317343

318344
conf = libvirt_driver.get_config(connection_info, self.disk_info)
@@ -324,12 +350,12 @@ def test_libvirt_quobyte_driver_mount(self,
324350
export_mnt_base,
325351
mock.ANY)
326352
mock_validate_volume.assert_called_with(export_mnt_base)
353+
mock_umount_volume.assert_called_once_with(
354+
libvirt_driver._get_mount_path(connection_info))
327355

328-
@mock.patch.object(quobyte, 'validate_volume')
356+
@mock.patch.object(quobyte, 'validate_volume', return_value=True)
329357
@mock.patch.object(quobyte, 'umount_volume')
330-
@mock.patch.object(libvirt_utils, 'is_mounted', return_value=True)
331-
def test_libvirt_quobyte_driver_umount(self, mock_is_mounted,
332-
mock_umount_volume,
358+
def test_libvirt_quobyte_driver_umount(self, mock_umount_volume,
333359
mock_validate_volume):
334360
mnt_base = '/mnt'
335361
self.flags(quobyte_mount_point_base=mnt_base, group='libvirt')
@@ -352,7 +378,9 @@ def test_libvirt_quobyte_driver_umount(self, mock_is_mounted,
352378
libvirt_driver.disconnect_volume(connection_info,
353379
mock.sentinel.instance)
354380

355-
mock_validate_volume.assert_called_once_with(export_mnt_base)
381+
mock_validate_volume.assert_has_calls([mock.call(export_mnt_base),
382+
mock.call(export_mnt_base),
383+
mock.call(export_mnt_base)])
356384
mock_umount_volume.assert_called_once_with(export_mnt_base)
357385

358386
@mock.patch.object(quobyte, 'validate_volume')
@@ -385,15 +413,14 @@ def test_libvirt_quobyte_driver_already_mounted(self,
385413
mock.sentinel.instance)
386414

387415
mock_umount_volume.assert_called_once_with(export_mnt_base)
388-
mock_validate_volume.assert_called_once_with(export_mnt_base)
416+
mock_validate_volume.assert_has_calls([mock.call(export_mnt_base),
417+
mock.call(export_mnt_base)])
389418

419+
@mock.patch.object(quobyte, 'umount_volume')
390420
@mock.patch.object(quobyte, 'validate_volume')
391421
@mock.patch.object(quobyte, 'mount_volume')
392-
@mock.patch.object(libvirt_utils, 'is_mounted', return_value=False)
393-
def test_libvirt_quobyte_driver_qcow2(self, mock_is_mounted,
394-
mock_mount_volume,
395-
mock_validate_volume
396-
):
422+
def test_libvirt_quobyte_driver_qcow2(self, mock_mount_volume,
423+
mock_validate_volume, mock_umount):
397424
mnt_base = '/mnt'
398425
self.flags(quobyte_mount_point_base=mnt_base, group='libvirt')
399426
libvirt_driver = quobyte.LibvirtQuobyteVolumeDriver(self.fake_host)
@@ -408,6 +435,8 @@ def test_libvirt_quobyte_driver_qcow2(self, mock_is_mounted,
408435

409436
export_mnt_base = os.path.join(mnt_base,
410437
utils.get_hash_str(quobyte_volume))
438+
mock_validate_volume.side_effect = [nova_exception.StaleVolumeMount(
439+
"This shall fail."), True, True]
411440

412441
libvirt_driver.connect_volume(connection_info, mock.sentinel.instance)
413442
conf = libvirt_driver.get_config(connection_info, self.disk_info)
@@ -423,6 +452,8 @@ def test_libvirt_quobyte_driver_qcow2(self, mock_is_mounted,
423452

424453
libvirt_driver.disconnect_volume(connection_info,
425454
mock.sentinel.instance)
455+
mock_umount.assert_has_calls([mock.call(export_mnt_base),
456+
mock.call(export_mnt_base)])
426457

427458
@mock.patch.object(libvirt_utils, 'is_mounted', return_value=True)
428459
def test_libvirt_quobyte_driver_mount_non_quobyte_volume(self,

nova/virt/libvirt/volume/quobyte.py

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
# License for the specific language governing permissions and limitations
1414
# under the License.
1515

16-
import errno
1716
import os
1817

1918
from oslo_concurrency import processutils
@@ -26,7 +25,6 @@
2625
from nova import exception as nova_exception
2726
from nova.i18n import _
2827
from nova import utils
29-
from nova.virt.libvirt import utils as libvirt_utils
3028
from nova.virt.libvirt.volume import fs
3129

3230
LOG = logging.getLogger(__name__)
@@ -75,12 +73,15 @@ def umount_volume(mnt_base):
7573

7674

7775
def validate_volume(mount_path):
78-
"""Runs a number of tests to be sure this is a (working) Quobyte mount"""
76+
"""Determine if the volume is a valid Quobyte mount.
77+
78+
Runs a number of tests to be sure this is a (working) Quobyte mount
79+
"""
7980
partitions = psutil.disk_partitions(all=True)
8081
for p in partitions:
8182
if mount_path != p.mountpoint:
8283
continue
83-
if p.device.startswith("quobyte@"):
84+
if p.device.startswith("quobyte@") or p.fstype == "fuse.quobyte":
8485
statresult = os.stat(mount_path)
8586
# Note(kaisers): Quobyte always shows mount points with size 0
8687
if statresult.st_size == 0:
@@ -90,10 +91,10 @@ def validate_volume(mount_path):
9091
msg = (_("The mount %(mount_path)s is not a "
9192
"valid Quobyte volume. Stale mount?")
9293
% {'mount_path': mount_path})
93-
raise nova_exception.InvalidVolume(msg)
94+
raise nova_exception.StaleVolumeMount(msg, mount_path=mount_path)
9495
else:
95-
msg = (_("The mount %(mount_path)s is not a valid"
96-
" Quobyte volume according to partition list.")
96+
msg = (_("The mount %(mount_path)s is not a valid "
97+
"Quobyte volume according to partition list.")
9798
% {'mount_path': mount_path})
9899
raise nova_exception.InvalidVolume(msg)
99100
msg = (_("No matching Quobyte mount entry for %(mount_path)s"
@@ -128,39 +129,40 @@ def connect_volume(self, connection_info, instance):
128129
data = connection_info['data']
129130
quobyte_volume = self._normalize_export(data['export'])
130131
mount_path = self._get_mount_path(connection_info)
131-
mounted = libvirt_utils.is_mounted(mount_path,
132-
SOURCE_PROTOCOL
133-
+ '@' + quobyte_volume)
134-
if mounted:
135-
try:
136-
os.stat(mount_path)
137-
except OSError as exc:
138-
if exc.errno == errno.ENOTCONN:
139-
mounted = False
140-
LOG.info('Fixing previous mount %s which was not'
141-
' unmounted correctly.', mount_path)
142-
umount_volume(mount_path)
132+
try:
133+
validate_volume(mount_path)
134+
mounted = True
135+
except nova_exception.StaleVolumeMount:
136+
mounted = False
137+
LOG.info('Fixing previous mount %s which was not '
138+
'unmounted correctly.', mount_path)
139+
umount_volume(mount_path)
140+
except nova_exception.InvalidVolume:
141+
mounted = False
143142

144143
if not mounted:
145144
mount_volume(quobyte_volume,
146145
mount_path,
147146
CONF.libvirt.quobyte_client_cfg)
148147

149-
validate_volume(mount_path)
148+
try:
149+
validate_volume(mount_path)
150+
except (nova_exception.InvalidVolume,
151+
nova_exception.StaleVolumeMount) as nex:
152+
LOG.error("Could not mount Quobyte volume: %s", nex)
150153

151154
@utils.synchronized('connect_qb_volume')
152155
def disconnect_volume(self, connection_info, instance):
153156
"""Disconnect the volume."""
154157

155-
quobyte_volume = self._normalize_export(
156-
connection_info['data']['export'])
157158
mount_path = self._get_mount_path(connection_info)
158-
159-
if libvirt_utils.is_mounted(mount_path, 'quobyte@' + quobyte_volume):
160-
umount_volume(mount_path)
159+
try:
160+
validate_volume(mount_path)
161+
except (nova_exception.InvalidVolume,
162+
nova_exception.StaleVolumeMount) as exc:
163+
LOG.warning("Could not disconnect Quobyte volume mount: %s", exc)
161164
else:
162-
LOG.info("Trying to disconnected unmounted volume at %s",
163-
mount_path)
165+
umount_volume(mount_path)
164166

165167
def _normalize_export(self, export):
166168
protocol = SOURCE_PROTOCOL + "://"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
features:
3+
- |
4+
The Quobyte Nova volume driver now supports identifying Quobyte
5+
mounts via the mounts fstype field, which is used by Quobyte 2.x
6+
clients. The previous behaviour is deprecated and may be removed
7+
from the Quobyte clients in the future.
8+
fixes:
9+
- |
10+
Fixes a bug that caused Nova to fail on mounting Quobyte volumes
11+
whose volume URL contained multiple registries.

0 commit comments

Comments
 (0)