]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
I/O rate limit for volume copy with qemu-img convert
authorTomoki Sekiyama <tomoki.sekiyama@hds.com>
Fri, 13 Jun 2014 22:49:24 +0000 (18:49 -0400)
committerTomoki Sekiyama <tomoki.sekiyama@hds.com>
Tue, 17 Jun 2014 19:09:37 +0000 (15:09 -0400)
Currently, volume copy operations consumes disk I/O bandwidth heavily and
may slow down the other guest instances. This patch limits bandwidth for
volume copy to mitigate interference to other instance performance.

In this implementation, 'qemu-img' is put in a blkio cgroup for throttling,
when CONF.volume_copy_bps_limit is set to non-zero.

Change-Id: I8403d4bcafc4279654034da97aa9b6c097f9e649
Signed-off-by: Tomoki Sekiyama <tomoki.sekiyama@hds.com>
Implements: blueprint limit-volume-copy-bandwidth

cinder/image/image_utils.py
cinder/tests/test_image_utils.py

index 883f736350d2944d3a2b90ac7a474cbbfdf456fc..2ffe80354f926add7b2cd44b824e47f9d8cab3ea 100644 (file)
@@ -59,9 +59,13 @@ def qemu_img_info(path):
     return imageutils.QemuImgInfo(out)
 
 
-def convert_image(source, dest, out_format):
+def convert_image(source, dest, out_format, bps_limit=None):
     """Convert image to other format."""
     cmd = ('qemu-img', 'convert', '-O', out_format, source, dest)
+    cgcmd = volume_utils.setup_blkio_cgroup(source, dest, bps_limit)
+    if cgcmd:
+        cmd = tuple(cgcmd) + cmd
+        cmd += ('-t', 'none')  # required to enable ratelimit by blkio cgroup
     utils.execute(*cmd, run_as_root=True)
 
 
@@ -215,7 +219,8 @@ def fetch_to_volume_format(context, image_service,
         # malicious.
         LOG.debug("%s was %s, converting to %s " % (image_id, fmt,
                                                     volume_format))
-        convert_image(tmp, dest, volume_format)
+        convert_image(tmp, dest, volume_format,
+                      bps_limit=CONF.volume_copy_bps_limit)
 
         data = qemu_img_info(dest)
         if data.file_format != volume_format:
@@ -251,7 +256,8 @@ def upload_volume(context, image_service, image_meta, volume_path,
     with fileutils.remove_path_on_error(tmp):
         LOG.debug("%s was %s, converting to %s" %
                   (image_id, volume_format, image_meta['disk_format']))
-        convert_image(volume_path, tmp, image_meta['disk_format'])
+        convert_image(volume_path, tmp, image_meta['disk_format'],
+                      bps_limit=CONF.volume_copy_bps_limit)
 
         data = qemu_img_info(tmp)
         if data.file_format != image_meta['disk_format']:
index 03c4dcb400a56b421f1ceb8094e746774d4a5102..39666bd30c1f9e08ed9d656132397f5df239e4a4 100644 (file)
@@ -19,6 +19,8 @@ import contextlib
 import mox
 import tempfile
 
+from oslo.config import cfg
+
 from cinder import context
 from cinder import exception
 from cinder.image import image_utils
@@ -26,6 +28,9 @@ from cinder.openstack.common import processutils
 from cinder import test
 from cinder import units
 from cinder import utils
+from cinder.volume import utils as volume_utils
+
+CONF = cfg.CONF
 
 
 class FakeImageService:
@@ -191,17 +196,21 @@ class TestUtils(test.TestCase):
 
         self.assertEqual(str(inf), TEST_STR)
 
-    def _test_fetch_to_raw(self, has_qemu=True, src_inf=None, dest_inf=None):
+    def _test_fetch_to_raw(self, has_qemu=True, src_inf=None, dest_inf=None,
+                           bps_limit=0):
         mox = self._mox
         mox.StubOutWithMock(image_utils, 'create_temporary_file')
         mox.StubOutWithMock(utils, 'execute')
         mox.StubOutWithMock(image_utils, 'fetch')
+        mox.StubOutWithMock(volume_utils, 'setup_blkio_cgroup')
 
         TEST_INFO = ("image: qemu.qcow2\n"
                      "file format: raw\n"
                      "virtual size: 0 (0 bytes)\n"
                      "disk size: 0")
 
+        CONF.set_override('volume_copy_bps_limit', bps_limit)
+
         image_utils.create_temporary_file().AndReturn(self.TEST_DEV_PATH)
 
         test_qemu_img = utils.execute(
@@ -223,9 +232,19 @@ class TestUtils(test.TestCase):
                 )
 
         if has_qemu and dest_inf:
-            utils.execute(
-                'qemu-img', 'convert', '-O', 'raw',
-                self.TEST_DEV_PATH, self.TEST_DEV_PATH, run_as_root=True)
+            if bps_limit:
+                prefix = ('cgexec', '-g', 'blkio:test')
+                postfix = ('-t', 'none')
+            else:
+                prefix = postfix = ()
+            cmd = prefix + ('qemu-img', 'convert', '-O', 'raw',
+                            self.TEST_DEV_PATH, self.TEST_DEV_PATH) + postfix
+
+            volume_utils.setup_blkio_cgroup(
+                self.TEST_DEV_PATH, self.TEST_DEV_PATH,
+                bps_limit).AndReturn(prefix)
+
+            utils.execute(*cmd, run_as_root=True)
 
             utils.execute(
                 'env', 'LC_ALL=C', 'qemu-img', 'info',
@@ -254,6 +273,26 @@ class TestUtils(test.TestCase):
                                  mox.IgnoreArg())
         self._mox.VerifyAll()
 
+    def test_fetch_to_raw_with_bps_limit(self):
+        SRC_INFO = ("image: qemu.qcow2\n"
+                    "file_format: qcow2 \n"
+                    "virtual_size: 50M (52428800 bytes)\n"
+                    "cluster_size: 65536\n"
+                    "disk_size: 196K (200704 bytes)")
+        DST_INFO = ("image: qemu.raw\n"
+                    "file_format: raw\n"
+                    "virtual_size: 50M (52428800 bytes)\n"
+                    "cluster_size: 65536\n"
+                    "disk_size: 196K (200704 bytes)\n")
+
+        self._test_fetch_to_raw(src_inf=SRC_INFO, dest_inf=DST_INFO,
+                                bps_limit=1048576)
+
+        image_utils.fetch_to_raw(context, self._image_service,
+                                 self.TEST_IMAGE_ID, self.TEST_DEV_PATH,
+                                 mox.IgnoreArg())
+        self._mox.VerifyAll()
+
     def test_fetch_to_raw_no_qemu_img(self):
         self._test_fetch_to_raw(has_qemu=False)
 
@@ -385,19 +424,29 @@ class TestUtils(test.TestCase):
 
         self._test_fetch_verify_image(TEST_RETURN)
 
-    def test_upload_volume(self):
+    def test_upload_volume(self, bps_limit=0):
         image_meta = {'id': 1, 'disk_format': 'qcow2'}
         TEST_RET = "image: qemu.qcow2\n"\
                    "file_format: qcow2 \n"\
                    "virtual_size: 50M (52428800 bytes)\n"\
                    "cluster_size: 65536\n"\
                    "disk_size: 196K (200704 bytes)"
+        if bps_limit:
+            CONF.set_override('volume_copy_bps_limit', bps_limit)
+            prefix = ('cgexec', '-g', 'blkio:test')
+            postfix = ('-t', 'none')
+        else:
+            prefix = postfix = ()
+        cmd = prefix + ('qemu-img', 'convert', '-O', 'qcow2',
+                        mox.IgnoreArg(), mox.IgnoreArg()) + postfix
 
         m = self._mox
         m.StubOutWithMock(utils, 'execute')
+        m.StubOutWithMock(volume_utils, 'setup_blkio_cgroup')
 
-        utils.execute('qemu-img', 'convert', '-O', 'qcow2',
-                      mox.IgnoreArg(), mox.IgnoreArg(), run_as_root=True)
+        volume_utils.setup_blkio_cgroup(mox.IgnoreArg(), mox.IgnoreArg(),
+                                        bps_limit).AndReturn(prefix)
+        utils.execute(*cmd, run_as_root=True)
         utils.execute(
             'env', 'LC_ALL=C', 'qemu-img', 'info',
             mox.IgnoreArg(), run_as_root=True).AndReturn(
@@ -410,6 +459,9 @@ class TestUtils(test.TestCase):
                                   image_meta, '/dev/loop1')
         m.VerifyAll()
 
+    def test_upload_volume_with_bps_limit(self):
+        self.test_upload_volume(bps_limit=1048576)
+
     def test_upload_volume_with_raw_image(self):
         image_meta = {'id': 1, 'disk_format': 'raw'}
         mox = self._mox