From 53073d19217778d6af48e9eb7d8f96ca7a92b931 Mon Sep 17 00:00:00 2001 From: zhangsong Date: Mon, 9 Nov 2015 23:00:08 +0800 Subject: [PATCH] Fix the bug of OSError when convert image MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 --- cinder/image/image_utils.py | 12 ++++- cinder/tests/unit/test_image_utils.py | 74 ++++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 9 deletions(-) diff --git a/cinder/image/image_utils.py b/cinder/image/image_utils.py index 08803f30a..e9ed86f58 100644 --- a/cinder/image/image_utils.py +++ b/cinder/image/image_utils.py @@ -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") diff --git a/cinder/tests/unit/test_image_utils.py b/cinder/tests/unit/test_image_utils.py index 6bce1140f..492ebc85f 100644 --- a/cinder/tests/unit/test_image_utils.py +++ b/cinder/tests/unit/test_image_utils.py @@ -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.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_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_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.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): + 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_stat.return_value.st_size = 1048576 + 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) -- 2.45.2