Wait for device to be mapped

There's a race condition when trying to perform file injection without
libguestfs, which causes a fallback to nbd device. Although the kpartx
command succeeds, it does so after the code has tested for success, so
Nova thinks it failed.

Retry a few times to avoid this.

Co-Authored-By: Paul Carlton <paul.carlton2@hp.com>
Change-Id: Ie5c186562475cd56c55520ad7123f47a0130b2a4
Closes-Bug: #1428639
Closes-Bug: #1484586
This commit is contained in:
Alexis Lee 2015-08-21 13:58:06 +01:00 committed by Alexis Lee
parent 4c18867424
commit 2c1b19761b
2 changed files with 34 additions and 10 deletions

View File

@ -59,9 +59,11 @@ class MountTestCase(test.NoDBTestCase):
self.fail("Unexpected call with: %s" % filename)
return exists_effect
def _check_calls(self, exists, filenames):
def _check_calls(self, exists, filenames, trailing=0):
self.assertEqual([mock.call(x) for x in filenames],
exists.call_args_list)
exists.call_args_list[:len(filenames)])
self.assertEqual([mock.call(MAP_PARTITION)] * trailing,
exists.call_args_list[len(filenames):])
@mock.patch('os.path.exists')
def test_map_dev_partition_search(self, exists):
@ -79,8 +81,7 @@ class MountTestCase(test.NoDBTestCase):
AUTOMAP_PARTITION: False,
MAP_PARTITION: [False, True]})
mount = self._test_map_dev_with_trycmd(PARTITION)
self._check_calls(exists,
[ORIG_DEVICE, AUTOMAP_PARTITION, MAP_PARTITION, MAP_PARTITION])
self._check_calls(exists, [ORIG_DEVICE, AUTOMAP_PARTITION], 2)
self.assertEqual("", mount.error)
self.assertTrue(mount.mapped)
@ -91,11 +92,22 @@ class MountTestCase(test.NoDBTestCase):
AUTOMAP_PARTITION: False,
MAP_PARTITION: False})
mount = self._test_map_dev_with_trycmd(PARTITION)
self._check_calls(exists,
[ORIG_DEVICE, AUTOMAP_PARTITION, MAP_PARTITION, MAP_PARTITION])
self._check_calls(exists, [ORIG_DEVICE, AUTOMAP_PARTITION],
api.MAX_FILE_CHECKS + 1)
self.assertNotEqual("", mount.error)
self.assertFalse(mount.mapped)
@mock.patch('os.path.exists')
def test_map_dev_error_then_pass(self, exists):
exists.side_effect = self._exists_effect({
ORIG_DEVICE: True,
AUTOMAP_PARTITION: False,
MAP_PARTITION: [False, False, True]})
mount = self._test_map_dev_with_trycmd(PARTITION)
self._check_calls(exists, [ORIG_DEVICE, AUTOMAP_PARTITION], 3)
self.assertEqual("", mount.error)
self.assertTrue(mount.mapped)
@mock.patch('os.path.exists')
def test_map_dev_automap(self, exists):
exists.side_effect = self._exists_effect({

View File

@ -17,6 +17,7 @@ import os
import time
from oslo_log import log as logging
from oslo_service import loopingcall
from oslo_utils import importutils
from nova import exception
@ -27,6 +28,8 @@ from nova.virt.image import model as imgmodel
LOG = logging.getLogger(__name__)
MAX_DEVICE_WAIT = 30
MAX_FILE_CHECKS = 6
FILE_CHECK_INTERVAL = 0.25
class Mount(object):
@ -200,15 +203,24 @@ class Mount(object):
_out, err = utils.trycmd('kpartx', '-a', self.device,
run_as_root=True, discard_warnings=True)
@loopingcall.RetryDecorator(
max_retry_count=MAX_FILE_CHECKS - 1,
max_sleep_time=FILE_CHECK_INTERVAL,
exceptions=IOError)
def recheck_path(map_path):
if not os.path.exists(map_path):
raise IOError()
# Note kpartx does nothing when presented with a raw image,
# so given we only use it when we expect a partitioned image, fail
if not os.path.exists(map_path):
try:
recheck_path(map_path)
self.mapped_device = map_path
self.mapped = True
except IOError:
if not err:
err = _('partition %s not found') % self.partition
self.error = _('Failed to map partitions: %s') % err
else:
self.mapped_device = map_path
self.mapped = True
elif self.partition and os.path.exists(automapped_path):
# Note auto mapping can be enabled with the 'max_part' option
# to the nbd or loop kernel modules. Beware of possible races