]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Fix the bug of OSError when convert image
authorzhangsong <zhangsong@cmss.chinamobile.com>
Mon, 9 Nov 2015 15:00:08 +0000 (23:00 +0800)
committerzhangsong <zhangsong@cmss.chinamobile.com>
Mon, 23 Nov 2015 14:44:39 +0000 (22:44 +0800)
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
cinder/tests/unit/test_image_utils.py

index 08803f30a63f91fc0f1616d010fac4261658b303..e9ed86f5834fac8a5ba6419203a95d4306414db5 100644 (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")
index 6bce1140fec86739c0ddfa066c8a7fd3413587e8..492ebc85f579aab0881e0f1e940568612c6b175b 100644 (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.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)