Fix the bug of OSError when convert image

When I try to convert a image use image_utils.convert_image() method,
an error occurred like this :
'''
OSError: [Errno 2] No such file or directory:
'sheepdog:10.133.17.61:7000:volume-a0a70f9b-a50e-4369-885f-c41a894c9fe5'
'''
The reason is that in some cluster storage systems, like
ceph/sheepdog, QEMU can access an image directly via their private
protocol, and there’s no need to map an image as a block device on
the host. In this case, the qemu-img convert command may like:

    #qemu-img convert -O raw sheepdog:Ip:port:image_name temp_file
    #qemu-img convert -O raw rbd:pool_name/image_name temp_file

The source path may be 'sheepdog:Ip:port:image_name' or
'rbd:pool_name/image_name', it doesn't exist in OS. So, when it runs
the os.stat(source) in image_utils.convert_image(source,dest,out_format)
method, an OSError would be raised.

We can use qemu_img_info method instead to resolve this problem, because
the 'qemu-img info' command can always get the image size info which has
support qemu-img tool. Here we capture a ValueError just in case, but it
only need to give a warning message, because the image has been successfully
converted.

Change-Id: I5fd1e51840972a67053b85a76f8e001fa8148ad7
Closes-Bug: #1514442
This commit is contained in:
zhangsong 2015-11-09 23:00:08 +08:00
parent a6f92ceaf0
commit 53073d1921
2 changed files with 80 additions and 12 deletions

View File

@ -126,7 +126,17 @@ def _convert_image(prefix, source, dest, out_format, run_as_root=True):
# some incredible event this is 0 (cirros image?) don't barf
if duration < 1:
duration = 1
fsz_mb = os.stat(source).st_size / units.Mi
try:
image_size = qemu_img_info(source, run_as_root=True).virtual_size
except ValueError as e:
msg = _LI("The image was successfully converted, but image size "
"is unavailable. src %(src)s, dest %(dest)s. %(error)s")
LOG.info(msg, {"src": source,
"dest": dest,
"error": e})
return
fsz_mb = image_size / units.Mi
mbps = (fsz_mb / duration)
msg = ("Image conversion details: src %(src)s, size %(sz).2f MB, "
"duration %(duration).2f sec, destination %(dest)s")

View File

@ -114,15 +114,15 @@ class TestQemuImgInfo(test.TestCase):
class TestConvertImage(test.TestCase):
@mock.patch('cinder.image.image_utils.os.stat')
@mock.patch('cinder.image.image_utils.qemu_img_info')
@mock.patch('cinder.utils.execute')
@mock.patch('cinder.utils.is_blk_device', return_value=True)
def test_defaults_block_dev(self, mock_isblk, mock_exec,
mock_stat):
def test_defaults_block_dev_with_size_info(self, mock_isblk,
mock_exec, mock_info):
source = mock.sentinel.source
dest = mock.sentinel.dest
out_format = mock.sentinel.out_format
mock_stat.return_value.st_size = 1048576
mock_info.return_value.virtual_size = 1048576
throttle = throttling.Throttle(prefix=['cgcmd'])
with mock.patch('cinder.volume.utils.check_for_odirect_support',
@ -146,17 +146,75 @@ class TestConvertImage(test.TestCase):
'-O', out_format, source, dest,
run_as_root=True)
@mock.patch('cinder.volume.utils.check_for_odirect_support',
return_value=True)
@mock.patch('cinder.image.image_utils.os.stat')
@mock.patch('cinder.image.image_utils.qemu_img_info')
@mock.patch('cinder.utils.execute')
@mock.patch('cinder.utils.is_blk_device', return_value=False)
def test_defaults_not_block_dev(self, mock_isblk, mock_exec,
mock_stat, mock_odirect):
@mock.patch('cinder.utils.is_blk_device', return_value=True)
def test_defaults_block_dev_without_size_info(self, mock_isblk,
mock_exec,
mock_info):
source = mock.sentinel.source
dest = mock.sentinel.dest
out_format = mock.sentinel.out_format
mock_stat.return_value.st_size = 1048576
mock_info.side_effect = ValueError
throttle = throttling.Throttle(prefix=['cgcmd'])
with mock.patch('cinder.volume.utils.check_for_odirect_support',
return_value=True):
output = image_utils.convert_image(source, dest, out_format,
throttle=throttle)
mock_info.assert_called_once_with(source, run_as_root=True)
self.assertIsNone(output)
mock_exec.assert_called_once_with('cgcmd', 'qemu-img', 'convert',
'-t', 'none', '-O', out_format,
source, dest, run_as_root=True)
mock_exec.reset_mock()
with mock.patch('cinder.volume.utils.check_for_odirect_support',
return_value=False):
output = image_utils.convert_image(source, dest, out_format)
self.assertIsNone(output)
mock_exec.assert_called_once_with('qemu-img', 'convert',
'-O', out_format, source, dest,
run_as_root=True)
@mock.patch('cinder.volume.utils.check_for_odirect_support',
return_value=True)
@mock.patch('cinder.image.image_utils.qemu_img_info')
@mock.patch('cinder.utils.execute')
@mock.patch('cinder.utils.is_blk_device', return_value=False)
def test_defaults_not_block_dev_with_size_info(self, mock_isblk,
mock_exec,
mock_info,
mock_odirect):
source = mock.sentinel.source
dest = mock.sentinel.dest
out_format = mock.sentinel.out_format
mock_info.return_value.virtual_size = 1048576
output = image_utils.convert_image(source, dest, out_format)
self.assertIsNone(output)
mock_exec.assert_called_once_with('qemu-img', 'convert', '-O',
out_format, source, dest,
run_as_root=True)
@mock.patch('cinder.volume.utils.check_for_odirect_support',
return_value=True)
@mock.patch('cinder.image.image_utils.qemu_img_info')
@mock.patch('cinder.utils.execute')
@mock.patch('cinder.utils.is_blk_device', return_value=False)
def test_defaults_not_block_dev_without_size_info(self,
mock_isblk,
mock_exec,
mock_info,
mock_odirect):
source = mock.sentinel.source
dest = mock.sentinel.dest
out_format = mock.sentinel.out_format
mock_info.side_effect = ValueError
output = image_utils.convert_image(source, dest, out_format)